edc를 이용하여 간단한 3D 효과를 줄 수 있습니다.

정밀하게 조절하기가 힘들어서 혹은 조절할 수가 없어서

정교한 효과와는 거리가 있습니다만,

지루한 2D 효과에 지쳤다면 한 번쯤 도전해볼 필요가 있습니다.


이번 포스팅에서는 원근효과를 주는 방법에 대해 살펴보겠습니다.



안녕하세요, Tizen 개발자 윤진입니다.


3D 효과를 적용하기 위해서는 map 블록을 사용해야 합니다.

collections - group - parts - part - description - map

위의 구조의 종단에 map 블록이 있습니다.

description 에에 있다는 것은 program으로 map의 상태를 변경하여 3D 효과를 줄 수 있다는 겁니다.


코드 상에서 map을 보면 아래와 같은 필드들과 사용됩니다.

description {
        ..
        map {
            perspective: "다른 파트이름";
            light: "이름";
            on: 1;
            smooth: 1;
            perspective_on: 1;
            backface_cull: 1;
            alpha: 1;
            rotation {
                ...
            }
        }
        ...
    }


위의 map 블록에서 사용한 필드-

perspective, light, on, smooth, perspective_on, backface_cull, alpha, rotate

우선 위의 필드 중 확대/축소와 관련된 필드인 perspective를 살펴보도록 하겠습니다.


- perspective: [다른 파트이름];

perspective 필드에는 현재 파트의 zplane을 정할때 참조하는 파트를 적어줍니다.

참조하는 파트의 zplane에 따라 현재 파트의 zplane도 확정됩니다.


그렇다면, zplane은 무엇일까요?

zplane은 2차원의 x축과 y축에 이은 3차원의 z축을 의미합니다.

zplane의 설정값에 따라 파트의 멀고 가까움이 결정됩니다.

zplane의 값이 커지면 파트가 가까워져서 커지고,

zplane의 값이 작아지면 파트가 점점 멀어져서 작아집니다.


위의 참조하는 파트는 아래와 같이 perspective 블록을 사용하여 zplane을 설정해야합니다.

       part {
         name: "zoom_out";
         type: RECT;
         description {
           state: "default" 0.0;
           perspective {
             zplane: -140;
           }
           visible: 0;
         }
       } 


perspective 필드에 사용할 파트는 화면에 보이지 않아도 됩니다.

따라서 color 필드를 사용하지 않았고,

visible을 '0'으로 설정하였습니다.

하지만, 영역만큼은 반드시 가지고 있어야 하기에 RECT 타입을 사용하였습니다.

(SPACER 타입도 사용하면 안됩니다)

rel1 / rel2를 사용하진 않았지만,

default로 rel1은 0.0으로 rel2는 1.0으로 설정되니 영역이 잡힌거나 다름없습니다.

perspective는 이 영역의 정가운데 점 하나만을 perspective 로 활용합니다.

zplane은 -140이니 이 파트에 perspective를 걸어둔다면 파트가 작아질 것입니다.


       part {
         name: "zoom_in";
         type: RECT;
         description {
           state: "default" 0.0;
           perspective {
             zplane: 140;
           }
           visible: 0;
         }
       }

위의 파트는 zplane이 140입니다.

perspective로 위의 파트를 걸어두면 파트는 더 커지게 됩니다.



- on: [0 or 1];

map 블록을 활성화(1) 혹은 비활성화(0) 시킵니다.

default가 비활성화이기 때문에 map 블록을 사용하려면 반드시 on: 1;을 해주어야 합니다.



perspective를 확인하기 위해서 우선 rectangle을 3개 만들어보겠습니다.

       part {
         name: "rect_1";
         type: RECT;
         description {
             state: "default" 0.0;
             rel1 { relative: 0.1 0.1; to: "bg"; }
             rel2 { relative: 0.2 0.2; to: "bg"; }
             color: 100 0 100 255;
             visible: 1;
           }
         }
         part {
           name: "rect_2";
           type: RECT;
           description {
             state: "default" 0.0;
             rel1 { relative: 0.2 0.2; to: "bg"; }
             rel2 { relative: 0.3 0.3;  to: "bg"; }
             color: 150 0 150 255;
             visible: 1;
           }
         }
         part {
           name: "rect_3";
           type: RECT;
           description {
             state: "default" 0.0;
             rel1 { relative: 0.3 0.3; to: "bg"; }
             rel2 { relative: 0.4 0.4; to: "bg"; }
             color: 200 0 200 255;
             visible: 1;
           }
         }


색상을 점차 밝은 색으로 변경하며 세 개의 사각형을 그렸습니다.

세 개의 사각형 모두 relative 0.1의 폭과 높이를 가지고 있습니다.

실제로는 아래와 같이 보이겠지요.



이번에는 사각형에 각각의 perspective를 적용합니다.

제일 상단의 사각형에는 -140의 zplane이 적용된 파트를 보게 하고,

제일 하단의 사각형은 140의 zplane이 적용된 파트를 보게합니다.

중간에 있는 사각형은 zplane이 없습니다.


그리고 zplane이 적용된 파트는 전체화면을 영역으로 가지도록 만들겠습니다.

전체영역을 가지고 있다고 하더라도,

실제 perspective가 되는 것은 영역의 정가운데에 점입니다.

정가운데 점이 perspective 관점이 되어서 다른 파트의 원근을 조정합니다.

영역으로는 원근법의 중심점을 정하고,

zplane으로는 원근을 정합니다.


collections {
   group {
     name: "main";
     parts {
       part {
         name: "bg";
         type: RECT;
         description {
           state: "default" 0.0;
           color: 255 255 255 255;
           visible: 1;
         }
       }
       part {
         name: "zoom_out";
         type: RECT;
         description {
           state: "default" 0.0;
           perspective {
             zplane: -140;
           }
           visible: 0;
         }
       } 
       part {
         name: "zoom_in";
         type: RECT;
         description {
           state: "default" 0.0;
           perspective {
             zplane: 140;
           }
           visible: 0;
         }
       }
       part {
         name: "rect_1";
         type: RECT;
         description {
             state: "default" 0.0;
             rel1 { relative: 0.1 0.1; to: "bg"; }
             rel2 { relative: 0.2 0.2; to: "bg"; }
             color: 100 0 100 255;
             visible: 1;
             map {
               perspective: "zoom_out";
               on: 1;
             }
           }
         }
         part {
           name: "rect_2";
           type: RECT;
           description {
             state: "default" 0.0;
             rel1 { relative: 0.2 0.2; to: "bg"; }
             rel2 { relative: 0.3 0.3;  to: "bg"; }
             color: 150 0 150 255;
             visible: 1;
           }
         }
         part {
           name: "rect_3";
           type: RECT;
           description {
             state: "default" 0.0;
             rel1 { relative: 0.3 0.3; to: "bg"; }
             rel2 { relative: 0.4 0.4; to: "bg"; }
             color: 200 0 200 255;
             visible: 1;
             map {
               perspective: "zoom_in";
               on: 1;
             }
           }
         }
     }
  }
}


rect1 파트는 가운데 점에서 봤을때,

zplane이 -140이 되어 더 작아집니다.

더 작아지지만 원래 있던 위치보다 가운데 쪽으로 '많이' 다가오게 됩니다.

이는 소실점이 있는 원근법을 생각하시면 될 겁니다.

1m가 떨어진 두 물체를 가까이에서 보면 1m의 거리만큼으로 보이겠지만,

먼 거리에서 보면 1cm로 보일 수도 있겠죠.

물체가 멀어지면 멀어질수록 가운데로 다가오게 됩니다.


rect3 파트는 가운데 점에서 봤을때,

zplane이 140이 되어서 더 커집니다.

더 커지지만 원래 있던 위치보다 가운데 쪽에서 '조금' 멀어지게 됩니다.

완전히 가운데 있지 않는 한,

눈에 가까이 올수록 가운데에서 멀어지게 되는 것이지요.


종합하자면 아래 그림처럼,

rect1은 작아지고 rect2쪽으로 다가옵니다.

rect3는 커지면서 rect2로 멀어집니다.



오늘은 perspective에 대해서 알아보았습니다.

그럼 좋은 하루 보내세요~

끝_



Swallow 타입을 이용하여,

C에서 생성한 오브젝트를 edc에 넣을 수 있습니다.

사실 edc에서 사용하는 모든 타입은 C에서도 그대로 만들 수 있습니다.

이 말은 굳이 edc를 사용하지 않아도 C로만 화면 구성을 할 수 있다는 것이지요.


하지만, edc는 레이아웃에 특화 되어 있습니다.

화면 구성을 edc 빼고 논할 수는 없지요.

그래서 edc에서는 화면 구성을 하고,

나머지 필수 요소는 Swallow 타입을 이용하여,

C에서 코딩하곤 합니다.



안녕하세요, Tizen 개발자 윤진입니다.


SWALLOW는 파트의 기본적인 타입 중에 하나입니다.

C에서 생성한 오브젝트를 edc의 SWALLOW에 넣을 수 있습니다.


collections - group - part { ... type: SWALLOW ... }


SWALLOW 파트로만 이뤄진 그룹을 하나 보겠습니다.

repo. : git://review.tizen.org/framework/uifw/elementary

branch : tizen_2.3

source : data/themes/widgets/conformant.edc


group { name: "elm/conformant/base/default";
   parts {
      part { name: "elm.swallow.indicator";
         type: SWALLOW;
         description { state: "default" 0.0;
            fixed: 0 1;
            align: 0.0 0.0;
            rel2.relative: 1.0 0.0;
         }
      }
      part { name: "elm.swallow.content";
         type: SWALLOW;
         description { state: "default" 0.0;
            fixed: 0 1;
            align: 0.5 0.5;
            rel1.relative: 0.0 1.0;
            rel1.to_y: "elm.swallow.indicator";
            rel2.relative: 1.0 0.0;
            rel2.to_y: "elm.swallow.clipboard";
         }
      }
      part { name: "elm.swallow.clipboard";
         type: SWALLOW;
         description { state: "default" 0.0;
            fixed: 0 1;
            align: 0.0 1.0;
            rel1.relative: 0.0 0.0;
            rel1.to_y: "elm.swallow.virtualkeypad";
            rel2.relative: 1.0 0.0;
            rel2.to_y: "elm.swallow.virtualkeypad";
         }
      }
      part { name: "elm.swallow.virtualkeypad";
         type: SWALLOW;
         description { state: "default" 0.0;
            fixed: 0 1;
            align: 0.0 1.0;
            rel1.relative: 0.0 0.0;
            rel1.to_y: "elm.swallow.softkey";
            rel2.relative: 1.0 0.0;
            rel2.to_y: "elm.swallow.softkey";
         }
      }
      part { name: "elm.swallow.softkey";
         type: SWALLOW;
         description { state: "default" 0.0;
            fixed: 0 1;
            align: 0.0 1.0;
            rel1.relative: 0.0 1.0;
         }
      }
   }
}


위의 소스에 SWALLOW 타입을 사용할 때 주의해야하는 점이 있습니다.

우선, rel1 / rel2에서 영역을 제대로 잡아주지 않고 있는 것을 기억하세요.

위의 rel1 / rel2는 '면'으로 영역을 확정하지 않고 '점'이나 '선'이 되도록 설정하고 있습니다.

'점'이나 '선'은 크기가 없지요.


그렇다면 크기는 어디서 설정해주고 있는 것일까요?

위의 edc 파일에서는 min이 전혀 보이지 않으므로 c 파일에서 min을 설정해주었다고 유추할 수 있습니다.

C에서 evas_object_size_hint_min_set() 함수로 영역의 최소값을 정하였겠지요.

C 파일단에서 크기를 확정하고 Swallow 영역에 대입한다는 사실에 주목해야 합니다.


fixed가 사용되고 있는 점도 주의하세요.

(fixed와 관련해서는 이 포스트를 참고해주세요.)

c에서 설정한 min값으로 부모 영역의 크기에 영향을 주지 않기 위해서 fixed를 사용했습니다.


어쨌든 SWALLOW 파트에 오브젝트를 넣을거면, SWALLOW 파트는 fixed 처리하는게 좋습니다.

fixed를 1로 처리 하여 SWALLOW 파트의 크기에 직접적으로 영향을 미치지 못하게 하지요.


물론, c 파일에서 설정한 min값이 SWALLOW 타입의 파트에도 적용이 되어야 한다면,

fixed를 1로 처리해서는 안됩니다.

fixed: 0 0;과 같은 방식으로 설정하면 됩니다.


SWALLOW 파트 중에 "elm.swallow.indicator" 파트에 들어가는 부분을 C에서 발췌하였습니다.

SWALLOW에 넣을 오브젝트를 생성(_create_xxxx_indicator())하고,

elm_layout_content_set()을 사용하여 레이아웃 내에 위치하는 SWALLOW 파트에 넣고 있네요.


   if (indmode == ELM_WIN_INDICATOR_SHOW)
     {
        old_indi = elm_layout_content_get(conformant, INDICATOR_PART);

        //create new indicator
        if (!old_indi)
          {
             if ((sd->rot == 90)||(sd->rot == 270))
               {
                  if (!sd->landscape_indicator)
                    sd->landscape_indicator = _create_landscape_indicator(conformant);

                  if (!sd->landscape_indicator) return;

                  evas_object_show(sd->landscape_indicator);
                  elm_layout_content_set(conformant, INDICATOR_PART, sd->landscape_indicator);
               }
             else
               {
                  if (!sd->portrait_indicator)
                    sd->portrait_indicator = _create_portrait_indicator(conformant);

                  if (!sd->portrait_indicator) return;

                  evas_object_show(sd->portrait_indicator);
                  elm_layout_content_set(conformant, INDICATOR_PART, sd->portrait_indicator);
               }

          }
        elm_object_signal_emit(conformant, "elm,state,indicator,show", "elm");


C에서는 오브젝트를 만들고,

elm_layout_content_set()을 이용하여 SWALLOW 영역에 넣어줍니다.

오브젝트를 만들고 사이즈를 관리하는 법에 대해서는 차후에 다시 설명하겠습니다.


SWALLOW 파트를 만들고,

거기에 넣을 오브젝트를 C파일에서 만들어 간단하게나마 연동해 보았습니다.


오늘은 여기까지 하겠습니다.

그럼 좋은 하루 보내세요~

끝_


edc에 유려한 디자인을 입하기 위해서는 이미지가 필수입니다.

아무래도 기본 도형이나 텍스트만으로는 한계가 있겠지요.


이번 포스팅에서는 IMAGE 타입에 대해 살펴보도록 하겠습니다.

이미지 파트를 사용하여 이미지를 원하는대로 배치해보도록 하겠습니다.

이미지는 미리 준비해주세요.



안녕하세요, Tizen 개발자 윤진입니다.


이미지 타입의 파트를 사용하려면 이미지가 있어야겠지요.

이미지는 edc 파일과 하나가 되어 edj 파일을 만듭니다.

edc 내에서 확정하여 사용하는 이미지 파일은 edj로 컴파일될 때 합쳐질 수 있습니다.


블로깅을 하며 edc에서 edj로 컴파일되는 과정을 언급한 적이 없었는데요,

아주 간단하게 언급하자면,

edc는 텍스트 기반의 스크팁트이므로 edj라는 기계가 인지할 수 있는 언어로 컴파일해야하죠.

이 때 edj에는 edc에서 언급한 이미지 파일을 내재할 수 있습니다.

그 때 사용하는 블록이 images 블록입니다.



- images {}

collections와 동일한 레벨로 사용하는 images 블록이 있습니다.

이 블록에서 edj에 포함될 이미지들을 확정하지요.

edje_cc는 컴파일을 진행하며 images 블록을 참고하여 edj와 이미지들이 한 몸이 됩니다.

images 블록에는 image 필드를 넣을 수 있습니다.



- image: [이미지파일] [압축방법] [압축레벨];

이미지 필드에는 이미지파일과 압축방법이 명시됩니다.

이미지 파일은 절대경로/상대경로 어느 방식으로나 적어둘 수 있습니다.

여기에 명시한 경로값을 이미지 파트에서 그대로 사용하면 됩니다.


압축방법으로는 RAW, COMP, LOSSY[0~100], USER를 사용할 수 있습니다.

RAW는 날 것 그대로 사용하는 경우,

COMP는 무손실 압축으로 이미지를 사용하고,

LOSSY는 압축레벨과 함께 이미지를 손실 압축하고,

USER는 edj에 이미지를 포함하지 않습니다.


edc에서 이미지 파트를 사용하기 위한 준비가 완료되었습니다.

이제 이미지를 사용하기 위해,

파트의 타입을 IMAGE로 설정해봅시다.

그리고 세부 설정을 위해 part - description 내에 image 블록을 사용합니다.



- image {}

이미지 블록에는 여러 필드를 사용할 수 있습니다.

normal, tween, border, middle, border_scale, border_scale_by

위의 인자 정도만 익혀도 이미지를 사용하는데 무리가 없습니다.



- normal: "파일이름";

normal 상태에서 사용하는 이미지의 파일이름을 지정합니다.

파일은 절대경로 혹은 상대경로로 지정할 수 있습니다.

만약 edc 파일과 이미지 파일이 같은 위치에 있다면 경로를 써줄 필요가 없겠지요.


이미지 파트에 다수의 이미지를 설정할 수 있습니다.

2개 이상의 이미지가 설정된다면 애니메이션으로 동작하게 됩니다.

이때 normal로 설정하는 이미지가 시작/끝 프레임이 됩니다.

normal이 시작/끝 두 개의 역할을 하기 때문에 뭔가 어색한 애니메이션이 될 수도 있습니다.

차라리 normal을 시작으로 제한하고,

마지막 frame은 다른 이름으로 하는게 더 명확했을 거라 생각이 듭니다.



- tween: "파일이름";

tween에는 애니메이션에서 시작/끝 프레임을 제외한 다른 프레임을 설정합니다.

tween은 몇 개를 사용해도 상관없으며,

tween으로 입력하는 순서대로 애니메이션이 재생됩니다.

각각의 이미지가 재생되는 시간은 program을 이용하여 조절할 수 있습니다.

그 부분은 차후에 program을 설명하며 자세히 살펴보겠습니다.



- border: [왼쪽] [오른쪽] [위] [아래];

border에는 4가지 값을 입력합니다.

단위는 픽셀로써 이미지의 왼쪽/오른쪽/위/아래의 가장자리영역을 지정합니다.

이미지가 스케일에 따라 확대/축소된다고 하더라도,

위에 지정된 가장자리는 왜곡없이 표시됩니다.


라운드 형의 버튼 이미지를 사용하는 경우,

가로 혹은 세로가 상대적으로 더 길어지면 라운드 형태가 왜곡될 수 있습니다.

이를 막히 위해 border를 사용하여 가장자리 형태를 유지합니다.


border가 좌우만 설정되어 있다면,

이미지는 좌우로 확대가 가능합니다.

border가 상하로만 설정되어 있다면,

이미지는 상하로 확대가 가능할테지요.

border가 상하좌우로 모두 설정되어 있다면,

border는 9개 영역으로 나뉘게 됩니다.

이 중 좌상/좌하/우상/우하 영역만 고정 되고 다른 영역은 유동적으로 변합니다.



- middle: [0/1/NONE/DEFAULT/SOLID];

border가 설정되어 있을때 사용합니다.

border로 설정된 영역 외에 나머지 영역은 middle이지요.

0이나 NONE을 지정하면 middle 영역이 보이지 않습니다.

1이나 DEFAULT 혹은 SOLID로 지정하면 middle 영역이 보이지요.

아래 예시를 보면, middle에 대한 개념이 확실하게 잡히실 겁니다.


images {
  image: "btn.png" COMP;
}

collections {
   group {
     name: "main";
     parts {

       part {
         name: "bg";
         type: RECT;
         description {
           state: "default" 0.0;
           color: 255 255 255 255;
           visible: 1;
         }
       }

       part {
         name: "example";
         type: IMAGE;
         description {
           state: "default" 0.0;
           image {
             normal: "btn.png";
           }
           rel1.relative: 0.5 0.1;
           rel1.to: "bg";
           rel2.relative: 0.5 0.1;
           rel2.to: "bg";
           min: 61 56;
         }
       }

       part {
         name: "border_example";
         type: IMAGE;
         description {
           state: "default" 0.0;
           image {
             normal: "btn.png";
             border: 20 20 20 20;
           }
           rel1.relative: 0.5 0.3;
           rel1.to: "bg";
           rel2.relative: 0.5 0.3;
           rel2.to: "bg";
           min: 122 56;
         }
       }

       part {
         name: "middle_0_example";
         type: IMAGE;
         description {
           state: "default" 0.0;
           image {
             normal: "btn.png";
             border: 20 20 20 20;
             middle: 0;
           }
           rel1.relative: 0.5 0.5;
           rel1.to: "bg";
           rel2.relative: 0.5 0.5;
           rel2.to: "bg";
           min: 122 56;
         }
       }

       part {
         name: "middle_1_example";
         type: IMAGE;
         description {
           state: "default" 0.0;
           image {
             normal: "btn.png";
             border: 20 20 20 20;
             middle: 1;
           }
           rel1.relative: 0.5 0.7;
           rel1.to: "bg";
           rel2.relative: 0.5 0.7;
           rel2.to: "bg";
           min: 122 56;
         }
       }

       part {
         name: "middle_none_example";
         type: IMAGE;
         description {
           state: "default" 0.0;
           image {
             normal: "btn.png";
             border: 20 20 20 20;
             middle: DEFAULT;
           }
           rel1.relative: 0.5 0.9;
           rel1.to: "bg";
           rel2.relative: 0.5 0.9;
           rel2.to: "bg";
           min: 122 56;
         }
       }

     }
   }
}



- border_scale: [0 혹은 1];

border도 scale factor의 영향을 받을 수 있게 합니다.

scale에 대해선 이전 포스팅에서 언급한 바 있습니다.

화면 해상도가 바뀔 경우 스케일 팩터가 바뀔텐데요,

그에 맞춰 border에서 지정해둔 픽셀값에 스케일 팩터가 곱해져서 적용됩니다.



- border_scale_by: [0.0~];

border_scale을 enable할 때만 의미가 있는 필드입니다.

border 영역에 적용되는 스케일 팩터를 따로 설정할 수 있습니다.

border_scale_by로 설정한 스케일 값에 스케일 팩터를 곱한 값이 최종 적용됩니다.

스케일팩터가 2.0이고 border_scale_by가 0.5라면,

다른 scale 영역들이 2.0배씩 늘어날때,

border 영역은 2.0 x 0.5로 곱해져서 1.0으로(그대로) 보여지게 됩니다.



이미지에 대한 설명은 여기까지 하겠습니다.

그럼 좋은 하루 보내세요~

끝_

  1. Tizen Develer 2015.11.25 10:49

    상세한 설명 감사합니다 : )

edc파일로 문자열을 출력할 수도 있습니다.

문자열은 edc 파일에서 직접 입력할 수도 있고,

C파일에서 elm_object_part_text_set() 함수를 사용하여,

필요할 때마다 문자열을 입력할 수 있습니다.


문자열이 전혀 없는 앱은 거의 없을 겁니다.

복잡다단한 문자표현의 세계에 첫 발을 text 블록으로 내딛어 보겠습니다.



안녕하세요, Tizen 개발자 윤진입니다.


edc에 문자열을 지원하는 타입은 2가지가 있습니다.

- TEXT

- TEXTBLOCK

위의 두 가지 중에 TEXT 타입을 먼저 살펴보겠습니다.

TEXT는 TEXTBLOCK에 비하여 간단한 문자열을 표시하는데 사용하지요.

TEXT 타입이 TEXTBLOCK 타입에 비해 multiline이라든가 기능에는 제한은 있지만,

간단한 연산만 필요하기에 훨씬 빠릅니다.



- text {}

텍스트 파트에는 텍스트에 대한 추가정보를 기술하기 위한 text 블록이 필요합니다.

text {}는 part - description 내에 위치하지요.

description에 위치한다는 것은,

각 상태(state)에 따라 text의 설정값을 변경할 수 있다는 것이겠죠.

곧, program으로 텍스트의 모양새를 여러가지로 바꿀 수 있습니다.



- text: "문자열";

text 블록 내에 블록과 동일한 이름의 필드인 text를 사용할 수 있습니다.

text 필드에 명시한 문자열은 default로 사용되는 문자열입니다.

default는 외부에서 해당 텍스트 파트에 아무 것도 set하지 않으면 나타납니다.

edc에서 문자열을 확정할 수 있다면 text 필드를 사용하면 됩니다.



- font: "폰트명";

텍스트에 폰트를 지정할 수 있습니다.

font를 지정하려면 플랫폼에서 지정한 값을 넣어줘야 하겠지요.

"Sans"나 "Arial"과 같은 글꼴 이름을 적어주면 됩니다.

물론 해당 글꼴이 플랫폼에 탑재가 되어 있어야겠지요?



- size: [폰트사이즈];

폰트 사이즈를 입력할 수 있습니다.

양수를 적어넣으면 됩니다.



- align: [x축, 0.0~1.0] [y축, 0.0~1.0];

텍스트 파트에서 정렬을 하기 위해서 align을 사용합니다.

두 개의 인자를 적어주면 되는데 각각 x축/y축 기준입니다.

텍스트 파트에서 rel1/rel2로 지정한 파트의 영역 내에서,

텍스트의 배열을 어떻게 설정할지를 align으로 정하게 됩니다.


collections {
   group {
     name: "main";
   parts {
      part {
         name: "label";
         type: TEXT;
         scale: 1;
         description {
            state: "default" 0.0;
            color: 255 0 0 255;
            text {
               text: "TEXT";
               font: "Sans";
               size: 24;
               align: 0.5 0.5;
            }
         }
      }
   }
}

위의 코드에서는 화면 한가운데에 "TEXT"라는 문자열을 표시하지요.

폰트는 "Sans"이고 폰트사이즈는 24입니다.




- effect: [EFFECT];

폰트에 이펙트를 추가할 수 있습니다.

effect 필드는 description이 아닌 part에 넣어주어야 합니다.

그 말은 state에 따라 effect를 바꿀 수 없다는 것이겠지요.

이펙트는 종류가 많아보이긴 하지만 따지고 보면 윤곽선, 그림자로 귀결됩니다


•    PLAIN : effect가 없습니다.
•    OUTLINE  : 윤곽선
•    SOFT_OUTLINE : 부드러운 윤곽선
•    SHADOW : 그림자
•    SOFT_SHADOW : 부드러운 그림자
•    OUTLINE_SHADOW : 윤곽선 + 그림자
•    OUTLINE_SOFT_SHADOW : 윤곽선 + 부드러운 그림자
•    FAR_SHADOW : 멀리 떨어진 그림자
•    FAR_SOFT_SHADOW : 멀리 떨어진 부드러운 그림자
•    GLOW : 발광윤곽선


눈으로 직접 보는게 이해가 빠르겠네요.

아래처럼 9개의 이펙트를 위한 그룹을 만들어보겠습니다.


예시에 보면 color3가 쓰였는데요,

color3는 윤곽선이나 그림자 색상을 의미합니다.

color3는 color와 함께 description에 쓰이므로 state에 따라 바꿀 수 있습니다.


collections {
   group {
     name: "main";
     parts {
       part {
         name: "bg";
         type: RECT;
         description {
           state: "default" 0.0;
           color: 255 255 255 255;
         }
       }

       part {
         name: "PLAIN";
         type: TEXT;
         effect: PLAIN;
         description {
           state: "default" 0.0;
           color: 255 0 255 255;
           text {
             text: "PLAIN EFFECT";
             font: "Sans";
             size: 20;
             align: 0.5 0.0;
            }
         }
      }
     
      part {
         name: "OUTLINE";
         type: TEXT;
         effect: OUTLINE;
         description {
           state: "default" 0.0;
           color: 255 0 255 255;
           color3: 0 0 0 255;
           text {
              text: "OUTLINE EFFECT";
              font: "Sans";
              size: 20;
              align: 0.5 0.1;
            }
         }
      }
       part {
         name: "SOFT_OUTLINE";
         type: TEXT;
         effect: SOFT_OUTLINE;
         description {
           state: "default" 0.0;
           color: 255 0 255 255;
           color3: 0 0 0 255;
           text {
              text: "SOFT_OUTLINE EFFECT";
              font: "Sans";
              size: 20;
              align: 0.5 0.2;
            }
         }
      }
 
      part {
         name: "SHADOW";
         type: TEXT;
         effect: SHADOW;
         description {
           state: "default" 0.0;
           color: 255 0 255 255;
           color3: 0 0 0 255;
            text {
               text: "SHADOW EFFECT";
               font: "Sans";
               size: 20;
               align: 0.5 0.3;
            }
         }
      }
     
      part {
         name: "SOFT_SHADOW";
         type: TEXT;
         effect: SOFT_SHADOW;
         description {
           state: "default" 0.0;
           color: 255 0 255 255;
           color3: 0 0 0 255;
            text {
               text: "SOFT_SHADOW EFFECT";
               font: "Sans";
               size: 20;
               align: 0.5 0.4;
            }
         }
      }
 
      part {
         name: "OUTLINE_SHADOW";
         type: TEXT;
         effect: OUTLINE_SHADOW;
         description {
           state: "default" 0.0;
           color: 255 0 255 255;
           color3: 0 0 0 255;
            text {
               text: "OUTLINE_SHADOW EFFECT";
               font: "Sans";
               size: 20;
               align: 0.5 0.5;
            }
         }
      }
 
      part {
         name: "OUTLINE_SOFT_SHADOW";
         type: TEXT;
         effect: OUTLINE_SOFT_SHADOW;
         description {
           state: "default" 0.0;
           color: 255 0 255 255;
           color3: 0 0 0 255;
           text {
               text: "OUTLINE_SOFT_SHADOW EFFECT";
               font: "Sans";
               size: 20;
               align: 0.5 0.6;
            }
         }
      }
 
      part {
         name: "FAR_SHADOW";
         type: TEXT;
         effect: FAR_SHADOW;
         description {
           state: "default" 0.0;
           color: 255 0 255 255;
           color3: 0 0 0 255;
            text {
               text: "FAR_SHADOW EFFECT";
               font: "Sans";
               size: 20;
               align: 0.5 0.7;
            }
         }
      }
 
      part {
         name: "FAR_SOFT_SHADOW";
         type: TEXT;
         effect: FAR_SOFT_SHADOW;
         description {
            state: "default" 0.0;
            color: 255 0 255 255;
            color3: 0 0 0 255;
            text {
               text: "FAR_SOFT_SHADOW EFFECT";
               font: "Sans";
               size: 20;
               align: 0.5 0.8;
            }
         }
      }
 
      part {
         name: "GLOW";
         type: TEXT;
         effect: GLOW;
         description {
           state: "default" 0.0;
           color: 255 0 255 255;
           color3: 0 0 0 255;
            text {
               text: "GLOW EFFECT";
               font: "Sans";
              size: 20;
               align: 0.5 0.9;
            }
         }
      }
   }
}



오늘은 여기까지 하겠습니다.

그럼 좋은 하루 보내세요~

끝_

 
 
 
 

  1. YOhoho 2015.11.23 14:58

    color2는 윤곽선
    color3는 그림자 색상이군요

    • 안녕하세요, YOhoho님. 또 오셨군요. 감사합니다 :) 컬러 뒤에 붙는 숫자가 커질수록 텍스트에서 멀어지죠~ 근데 솔직히 필드이름에 숫자가 들어간게 맞는지는 모르겠네요 :)

오늘은 파트의 상태를 적어놓는 description에서,

가장 중요한 필드 중에 하나인 rel을 살펴보도록 하겠습니다.


rel은 파트의 위치와 크기를 관장하는 필드입니다.

형태를 가지고 있는 물체에는 위치와 크기 속성이 필수인만큼,

이 필드는 꼼꼼이 잘 살펴볼 필요가 있습니다.



안녕하세요, Tizen 개발자 윤진입니다.


rel 필드는,

collections - group - parts - part - description 아래에 위치하는 필드입니다.

description 안에 있다는 것만으로도,

rel 필드는 state에 따라 변화가 가능한 필드라는 것을 알 수 있습니다.


rel은 2개의 필드가 하나의 쌍으로 동작합니다.

rel1과 rel2가 각각 시작점, 끝점의 역할을 담당하게 되지요.

이 두 점으로 사각의 영역을 얻을 수 있습니다.

part는 이 두 점으로 기준으로 위치와 크기가 결정됩니다.



- rel1/rel2 

rel1과 rel2는 각각의 블록을 가지고 있습니다.

rel1 { ... }

rel2 { ... }

위와 같이 독립된 블록을 가집니다.

이름만 봐서는 rel3, rel4가 있어도 이상해보이진 않지만,

직사각형 영역으로 형태를 표현하는 것이 충분하다고 여기는지 2개의 필드만 사용하고 있습니다.

정말 그러한지는 각자 좀 더 고민해보시죠.


rel1은 왼쪽 상단 모서리를 의미합니다.

rel2는 오른쪽 하단 모서리를 의미하지요.

이 두 점만으로 직사각형을 표현할 수 있습니다.

rel1과 rel2 블록 안에 명시한 내용이 각각 모서리 위치를 구체화 합니다.



- relative: [X축] [Y축];

rel 블록에서 가장 먼저 등장하는 것이 relative입니다.

relative는 상대적인 위치를 나타냅니다.

화면에 표시되는 영역은 0.0 ~ 1.0 범위입니다.

x축/y축 기준으로 비율에 맞게 화면에 표시가 됩니다.


collections {
  group {
    name: "storycompiler";
    parts {
      part {
        name: "red_rectangle";
        type: RECT;
        description {
          state: "default" 0.0;
          rel1.relative: 0.0 0.5;
          rel2.relative: 1.0 1.0;
          color: 255 0 0 255;
          visible: 1;
        }
      }
    }
  }
}


rel1에 0.0 0.5를 적어놓으면,

시작점은 X축 기준으로는 0이고, y축 기준으로는 가운데입니다.

rel2에 1.0 1.0을 적어놓으면,

끝점은 x축/y축 모두 마지막 좌표입니다.

위의 코드를 출력한 결과는 아래와 같습니다.



0.0 이하 혹은 1.0 이상 영역은 화면 밖에 표시가 됩니다.

화면 밖에서 화면 안으로 들어오는 애니메이션 효과를 주고 싶을때,

-1.0 이나 2.0 과 같은 수치를 써주곤 합니다.


rel1과 rel2에 같은 값을 표기할 수도 있습니다.

시작점과 끝점이 같은 값이라면 결과는 '점'입니다.

점은 화면에 표시가 되지 않습니다.


그렇다면 왜 점을 사용하는 것일까요?

점은 min값과 좋은 짝이 됩니다.

점과 min을 함께 사용한다면,

점은 위치를 나타내고,

min이 크기를 지정하게 될 겁니다.

(min값과 관련된 내용은 이 포스팅을 참고해주세요)


만약 시작점과 끝점을 달리하여 영역을 만들고 추가로 min값을 넣는 경우,

논리적 모순이 생길 수도 있습니다.

rel1/rel2는 비율로 크기를 지정하는데,

그 크기가 min값과 다를 수 있기 때문입니다.


예를 들어, 화면의 사이즈가 100 x 100이고,

rel1은 0.0 0.0, rel2는 0.1 0.1으로 지정했다고 하죠.

rel로 잡은 영역은 10 x 10의 크기입니다.

하지만, 거기에 min값으로 50 50을 주면,

어떤 값을 선택해야할지 알 수가 없게됩니다.

10 10일까요? 50 50일까요?


바로 이 점이 edc를 어렵게 만드는 점이기도 한데요,

하나의 필드에 두 개의 역할(위치, 크기)를 부여하였기 때문에,

크기를 나타내는 min과 충돌이 날 수 있습니다.

따라서 min으로 크기를 설정하고자 하면,

반드시 rel로는 점을 만드시는게 좋습니다.



- to: [다른 파트 이름];

to는 굉장히 유용하게 사용되는 필드입니다.

위에서 rel1/rel2로 위치를 잡을때 화면 전체를 기준으로 설명하였는데요,

to 필드를 추가하면, to 필드에 명시한 파트를 기준으로 rel1 / rel2가 지정됩니다.

반대로 to 필드가 없으면 전체화면을 기준으로 위치를 잡게 되겠지요.


collections {
   group {
      name: "to_example";
 
      parts {
         part {
            name: "bg";
            type: RECT;
            scale: 1;
            description {
               state: "default" 0.0;
               rel1.relative: 0.0 0.0;
               rel2.relative: 1.0 1.0;
            }
         }
         part {
            name: "region";
            type: RECT;
            scale: 1;
            description {
               state: "default" 0.0;
               color: 255 0 0 255;
               rel1.relative: 0.2 0.2;
               rel1.to:"bg";
               rel2.relative:0.8 0.8;
               rel2.to:"bg";
            }
         }
      }
   }
}



- to_x: [다른 파트 이름];

- to_y: [다른 파트 이름];

위에서 설명한 to는 to_x와 to_y가 동일한 경우에 사용합니다.

만약 x축 기준영역과 y축 기준영역이 다를 경우,

to_x와 to_y를 사용하면 됩니다.

파트의 위치가 반드시 한 파트를 기준으로 정해야한다는 규칙은 없습니다.

서로 다른 두 개의 파트를 각각 x축 기준 y축 기준으로 사용하면 됩니다.


group {
      parts {
         part {
            name: "to_part";
            type: RECT;
            scale: 1;
            description {
               state: "default" 0.0;
               color: 255 0 0 255;
               rel1.relative: 0.2 0.2;
               rel1.to:"bg";
               rel2.relative:0.8 0.8;
               rel2.to_x: "some_part";
               rel2.to_y: "other_part";
               }             }          }       }    }


위의 코드에서 시작점은 bg를 기준으로 설정되었지만,

끝점의 x축은 some_part를 기준으로,

y축은 other_part를 기준으로 위치를 잡습니다.

 

- offset: [x축] [y축];

offset은 음수, 0, 양수를 사용할 수 있습니다.

단위는 픽셀입니다.

여기서 사용하는 값은 relative와 to로 결정된 위치에 마지막 수정을 가합니다.

보통 미세한 조정이 필요한 경우에 사용합니다.


offset에 적용되는 값은 이전에 설명한 scale과는 무관하게 동작합니다.

scale이 1로 설정되어 있더라도,

offset에 설정한 값은 적힌 값 그대로 사용됩니다.


보통 선을 만들거나 1~2픽셀 단위로 영역을 조정할 때 사용합니다.

-100 이나 100 픽셀로 offset를 주진 않습니다.

저 정도 크기로 영역을 조정하려면 relative를 사용하는 것이 맞습니다.


collections {
   group {
      parts {
         part {
            name: "part_three";
            type: RECT;
            scale: 1;
            description {
               state: "default" 0.0;
               color: 255 0 0 255;
               rel1 {
                  relative: 0.0 0.0;
                  to:"some_part";
                  offset: 1 -1;
               }
               rel2 {
                  relative:0.0 1.0;
                  to:"other_part";
                  offset: 1 -1;
               }
            }
         }
      }
   } 


위치에 대한 이야기는 여기서 마치겠습니다.

그럼 좋은 하루 보내세요~

끝_

  1. Tizen Develer 2015.11.23 17:10

    상세한 설명 감사드립니다ㅎㅎ 초보 개발자인데 이렇게 상세하게 설명되어 있는 블로그는 처음봐요!!
    열심히 보고 배우겠습니다~

part 블록을 처음 접했을때,

description 블록의 역할이 와닿지 않았습니다.

part 블록에 바로 넣어도 될만한 필드들을 굳이 description으로 감싸기 때문이죠.


하지만, program이 description 단위로 동작한다는 것을 본 이후에는,

description 파트도 치열한 고민의 산물이란 것을 깨달았습니다.



안녕하세요, Tizen 개발자 윤진입니다.


description 파트는 program에 의해 변할 수 있는 요소들을 넣어줍니다.

description에서 정의하는 모든 필드는 상태에 따라 변하는 필드라고 보시면 됩니다.

description의 필드값을 program으로 직접 변경하진 않고,

한 part 내에 위치한 다수의 description 사이에서 상태변환을 하게 되지요.


따라서 description의 이름은 name이라 부르지 않고 state라고 합니다.

part의 확정된 설정값이 아닌,

유동적으로 변모하는 상태를 나타내기 때문입니다.


우선 description에서 다루는 필드르 아래 코드에서 훑어보겠습니다.

description {
   state: "description_name" INDEX;
   inherit: "other_description_name" INDEX;
   visible: 1;
   align: 0.5 0.5;
   min: 0 0;
   max: -1 -1;
   fixed: 0 0;
   rel1 { ... }
   rel2 { ... }
   step: 0 0;
   aspect: 1 1;
}

위에 언급된 필드 중에 rel1, rel2는 다음 포스팅에서 살펴보도록 하겠습니다.

rel 블록 내에는 또 주요한 필드들이 있고 해당 필드에 대해서는 할 말이 좀 있어서요.

그 외의 필드는 하나씩 살펴보도록 하겠습니다.



- state: [이름] [인덱스];

state의 이름은 고유한 값이어야 합니다.

그리고 state 중 하나는 반드시 "default"여야만 합니다.

"default"는 파트가 load될 때 initial 상태이기 때문이지요.

그리고 인덱스는 0.0에서 1.0의 값으로 입력합니다.

인덱스가 있는 이유는 상태가 동일한 이름을 지닌다고 해도 인덱스로 구분지을 수 있기 때문이죠.

위에 언급한 "default"의 0.0 인덱스가 가장 기본이 되는 description입니다.



- inherit: [다른 description의 이름] [다른 description의 인덱스];

다른 description에서 설정한 필드를 그대로 가져올 수 있습니다.

inherit한 이후에 설정한 필드들은 inherit된 필드에 override됩니다.

inherit는 필드 한 두개만 다르고 나머지 필드는 모두 일치할 때 유용하게 사용됩니다.



- visible: [0 혹은 1];

화면에 보일지 감출지를 결정합니다.

0을 설정하면 해당 파트는 보여지지 않습니다.

1을 설정해야 비로소 화면에 보입니다.

화면에 보이지 않으면 이벤트도 그 파트에는 전달되지 않습니다.

원래부터 없는 셈치게 되죠.

default 값은 1입니다.

따로 적어주지 않으면 화면에 보이게 됩니다.



- align: [X축] [Y축];

align으로 X축 혹은 Y축 기준으로 정렬을 할 수 있습니다.

align을 지정하면 이 파트에 삽입되는 다른 요소의 정렬에 적용됩니다.

이 파트 자체가 다른 곳에 삽입될 때 적용되는 것이 아닙니다.

0.0 ~ 1.0의 값을 지정할 수 있습니다.

0.0은 왼편 정렬, 0.5는 가운데 정렬, 1.0은 오른편 정렬입니다.

double 형이니 만큼 0.25, 0.3 같은 값을 주어 애매한 정렬을 할 수도 있겠죠.

default 값은 X축, Y축 모두 가운데 정렬인 0.5 0.5입니다.



- min: [width] [height];

min 필드는 두 개의 값을 가지고 있습니다.

가로와 세로를 기준으로 파트의 min 사이즈를 지정할 수 있습니다.

min은 말 그대로 최소한의 크기입니다.

파트의 크기는 min에서 설정한 값으로 확정됩니다.

min 값은 relative로 설정한 값과 충돌을 일으킬 수 있기 때문에,

가급적이면 relative를 '점'으로 설정한 곳에 사용해야 합니다.

이에 대해선 relative를 설명할 때 다시 언급하도록 하겠습니다.



- max: [width] [height];

max 필드도 두 개의 값을 가지고 있습니다.

가로와 세로를 기준으로 파트의 max 사이즈를 지정할 수 있습니다.

max는 말 그대로 최대한의 크기입니다.

파트의 크기가 변하는 상황에서 참조하는 값입니다.

파트의 크기가 바로 max 값으로 지정되진 않지요.



- fixed: [가로, 0 or 1] [세로, 0 or 1];

fixed는 매우 중요한 필드입니다.

여기에 어떤 값을 주었느냐에 다라 다른 그룹에 지대한 영향을 미칠 수 있습니다.

인자는 2개인데 각각 가로와 세로를 의미합니다.

가로 혹은 세로를 기준으로 현재 파트의 min값의 영향력을 결정할 수 있습니다.


만약 fixed가 1이라면,

현재 파트의 min값은 현재 파트를 담고 있는 객체에 아무 영향을 주지 못합니다.

현재 파트를 담고 있는 객체의 크기가 현재 파트보다 작더라도,

현재 파트를 담을 수 있습니다.

'담다'는 용어보다는 붙여놓는다는 용어가 어울릴지도 모르겠다.


반대로 fixed가 0이라면,

현재 파트의 min값은 현재 파트를 담고 있는 객체에 영향을 줍니다.

현재 파트가 100 x 100이고 현재 파트를 담는 객체가 10 x 10인 경우,

부모 객체는 현재 파트의 min 값에 맞춰서 100 x 100이 됩니다.


따라서 fixed를 사용하려면 부모/자식 같의 크기를 잘 염두하여 사용해주세요.

특히 파트 사이즈가 변한다면 더욱 주의하셔야 합니다.

사이즈가 변하기 전에는 부모 파트의 크기가 제대로 잡혀있었는데,

자식의 사이즈가 변한 후에는 부모 파트도 같이 변해서 애초의 목적과 달라질 수 있습니다.



- step: [width] [height];

part를 resize하는 경우, step에 설정한 가로/세로 크기만큼의 단계로 확대/축소됩니다.

만약 step이 필요없다면 "0 0"으로 설정해두면 됩니다.

개인적으로 step을 사용해본 경험이 없어서 타이젠 소스를 뒤져보았습니다.

타이젠 플랫폼에서도 step을 사용한 예는 거의 없네요.


         part { name: "e.table.days"; type: TABLE;
            description { state: "default" 0.0;
               rel1.to_y: "e.table.daynames";
               rel1.relative: 0.0 1.0;
               rel1.offset: 2 2;
               rel2.offset: -3 -3;
               step: 7 5;
               table {
                  homogeneous: TABLE;
                  padding: 1 1;
                  align: 0.5 0.5;
                  min: 1 1;
               }
            }
         /* ... 생략 ... */


위처럼 TABLE을 만드는 경우 step을 사용하고 있습니다.

TABLE의 크기를 변경할 때 step에 명시된 크기 7, 5만큼 변경되겠지요.


위에서 7, 5인 이유는 캘린더를 위한 테이블이기에,

월화수목금토일을 담기 위한 7열에, 총 5주를 표기하기 위해서인 것으로 보입니다.

부모의 크기가 커지더라도 캘린더 테이블 파트는 7, 5 단위씩 커져기에,

모두가 균일한 크기를 유지할 수 있도록 합니다.



- aspect: [가로, 0 or 1] [세로, 0 or 1];

파트의 사이즈를 재조정하는 경우,

가로/세로의 사이즈가 일정 비율로 늘거나 줄어야 하는 경우가 있습니다.

이미지의 경우에는 특히 그러하지요.

그럴 때, aspect를 사용합니다.

가로/세로의 값으로 1로 주면, 이미지는 비율대로 크기가 조정됩니다.

가로/세로를 각각 1 혹은 0, 0 혹은 1로 설정하여 사용한 경우는 보지 못했습니다.

가로/세로 모두 1 아니면 0을 사용하지요.


- aspect_preference: [DIMENSION];

aspect_preference는 aspect와 함께 사용합니다.

값으로는 BOTH, VERTICAL, HORIZONTAL, NONE을 넣어줄 수 있습니다.

여기서 설정한 Dimension으로 비율이 결정됩니다.

보통의 이미지라면 BOTH로 해도 무방합니다.

세로를 기준으로 가로의 사이즈를 조절하고 싶다면 VERTICAL을,

가로를 기준으로 세로의 사이즈를 조절하고 싶다면 HORIZONTAL을,

그리고 따로 기준이 필요없다면 NONE으로 설정합니다.



- color: [Red] [Green] [Blue] [Alpha];

RGB와 Alpha 값을 설정할 수 있습니다.

RGB값으로 색상을 정하고 알파값으로 투명도를 정할 수 있습니다.

각 값은 0~255까지로 지정합니다.

숫자가 높아질수록 각각의 색상이 점차 뚜렷해집니다.

알파값의 경우 0이면 투명, 255에 가까울수록 불투명해집니다.



오늘은 여기까지 하겠습니다.

그럼 좋은 하루 보내세요~

끝_


part의 영역 중 일부만 노출해야하는 경우가 있습니다.

포토샵에 마스킹을 생각하시면 됩니다.

자신이 지정한 영역만 보이고 그 외의 영역은 아예 렌더링도 하지 않습니다.

바로 그러한 기능이 필요할 때 clip_to를 사용합니다.


clip이 자르다 혹은 깎다는 의미가 있는데요,

clip to로 지정한 영역 외에는 잘라버려서 보이지 않는다고 보면 됩니다.



안녕하세요, Tizen 개발자 윤진입니다.


clip_to: [another part's name];

clip_to는 파트블록 내에서 사용합니다.

필드값으로 다른 파트의 이름을 적어주어야 합니다.

현 파트의 영역 중 필드값으로 지정한 파트의 영역와 겹치는 부분만 렌더링하게 됩니다.


우선, 간단하게 이미지를 화면에 그려보도록 하겠습니다.

images {
  image: "bg.jpg" COMP;
}
collections {
  group {
    name: "storycompiler";
    parts {
      part {
        name: "bg";
        type: RECT;
        description {
          state: "default" 0.0;
          rel1.relative: 0.0 0.0;
          rel2.relative: 1.0 1.0;
          color: 0 0 0 255;
          visible: 1;
        }
      }
      part {
        name: "img";
        type: IMAGE;
        scale: 0;
        description {
          state: "default" 0.0;
          image.normal: "bg.jpg";
          rel1 { relative: 0.5 0.5; to: "bg"; }
          rel2 { relative: 0.5 0.5; to: "bg"; }
          min: 1145 425;
          fixed: 1 1;
          align: 0.5 0.5;
          visible: 1;
        }
      }
    }
  }
}


위의 코드에 있는 collections, group, parts, part 블록은 이미 지난 포스팅에서 설명을 드렸습니다.

image 타입에 대해서 아직 다루진 않았지만,

최소한의 기능만을 사용하였기에 코드만 봐도 이해에 어려움이 없으리라 생각합니다.


IMAGE 타입을 가진 img 파트는 bg 파트의 정중앙(0.5 0.5)에 위치하는 '점'입니다.

하지만, min값을 가지고 있기 때문에 min값만큼 영역이 늘어나게 됩니다.

그래서 나온 결과는 위의 캡쳐이미지와 같습니다.


코드에는 bg와 img 파트만 있는데요,

clip_to를 테스트하기 위하여 가운데 점으로부터 100 x 100짜리 사각형을 하나 만들어보겠습니다.


      part {
        name: "clip";
        type: RECT;
        description {
          state: "default" 0.0;
          rel1 { relative: 0.5 0.5; to: "bg"; }
          rel2 { relative: 0.5 0.5; to: "bg"; }
          color: 255 0 0 255;
          min: 100 100;
          align: 0.5 0.5;
          visible: 1;
        }
      }


위의 파트까지 포함하여 출력하면 아래 이미지를 볼 수 있습니다.

clip 사격형의 color는 RGBA 기준으로 255 0 0 0으로 붉게 두었습니다.


          color: 255 0 0 255;

자, 이제 clip_to를 위한 준비를 마쳤습니다.

img 파트에 clip_to 필드를 추가합니다.


      part {
        name: "img";
        type: IMAGE;
        scale: 0;
        clip_to: "clip";
        description {
          state: "default" 0.0;
          image.normal: "bg.jpg";
          rel1 { relative: 0.5 0.5; to: "bg"; }
          rel2 { relative: 0.5 0.5; to: "bg"; }
          min: 1145 425;
          fixed: 1 1;
          align: 0.5 0.5;
          visible: 1;
        }
      }


위처럼 한 줄 추가하면 굉장히 극적인 변화가 일어납니다.

clip 파트는 clip_to의 대상이 되기 때문에 img파트를 위한 '체'만 제공할 뿐입니다.

clip 파트가 '붉은색' 사각형이었기 때문에 '붉은 체'가 되겠지요.


img파트는 clip파트 영역만큼만 화면에 보이게 됩니다.

clip 파트가 '붉은 체'이기 때문에 붉게 물든 이미지가 보이겠네요.

그 외의 영역은 렌더링을 하지 않겠지요.

img보다 뒷편에 있는 bg의 검은 영역만 보이겠죠.



이전 이미지에서는 붉은 색 사각형에 가려져 보이지 않던 영역이 보이는 것을 확인할 수 있습니다.

붉은 색 사각형 영역만큼만 보이고 있습니다.


clip 영역에 애니메이션을 주면,

여러가지 재미난 효과를 추가할 수도 있습니다.


오늘은 여기까지 하겠습니다.

그럼 좋은 하루 보내세요~

끝_


edc의 part에 들어가는 필드 중에 ignore_flags가 있습니다.

무언가를 무시하기 위한 목적으로 만들어진 필드일텐데요,

이름만 봐서는 역할이 분명하게 다가오지 않네요.


이럴 때는 구글링으로 궁금증을 해소하거나,

EFL 소스를 뒤져보거나,

시간과 노력을 들여 순수한 삽질로 기능을 유추해야 합니다.

아니면, EFL 커미터에게 헬프를 외치면 됩니다.



안녕하세요, Tizen 개발자 윤진입니다.


우선, EFL 공식 사이트에서 설명을 찾아보겠습니다.

"Specifies whether events with the given flags should be ignored,

i.e., will not have the signals emitted to the parts."


플래그에 대한 이야기가 나옵니다.

플래그를 설정하면 이벤트가 무시될 수 있다고 하네요.

하지만, 단순히 ignore_flags에 넣는 값에 따라 이벤트를 무시한다면,

이전 포스팅에서 다룬 mouse_events와 다를 바가 없겠지요.


ignore_flags는 mouse_events처럼 무조건적으로 이벤트를 통제하는 것이 아닙니다.

경우에 따라 이벤트를 막거나 혹은 받을 수 있습니다.

그렇다면 그 '경우에 따라'는 어디에서 기인하는 것일까요?


파트 자신이 스스로 각각의 경우를 만들어낼 수 있을까요?

아닙니다. ignore_flags를 적어놓고 있는 파트 자신은 스스로 각각의 경우를 발생하지 못합니다.

대신 위의 파트와 관련이 있는 다른 파트들이 각각의 경우를 만들어냅니다.


어째서 이러한 구조가 필요한 것일까요?

스크롤이 가능한 영역을 생각해보지요.

스크롤이 가능한 영역에 다수의 객체가 삽입되었습니다.

스크롤을 하기 위해 press를 한 후 스크롤을 좌우로 움직입니다.

그리고 release를 하면 스크롤 내에 있던 객체는 이벤트를 받아야할까요?


분명 사용자는 스크롤을 목적으로 press - move - release를 했는데,

사용자의 의도와는 다르게 스크롤 내에 삽입된 객체가 release에 대한 이벤트를 처리할 수 있습니다.

사실 삽입객체는 이벤트를 ignore해야하는데 말이지요.


바로 여기에 ignore_flags 필드가 필요한 겁니다.

삽입객체에 "ignore_flags: ON_HOLD;"를 지정하면,

누군가가 ON_HOLD라고 외치는 순간 자신에게 오는 모든 이벤트는 무시하게 됩니다.

스크롤 와중에 삽입객체들은 이벤트를 받으면 안되지요.


이 사실을 파악하기 이해 먼저 evas 소스를 뒤져보았습니다.

Repository : git://review.tizen.org/framework/uifw/evas

Branch : tizen_2.4


/**
 * @brief Enumeration for events.
 */
typedef enum _Evas_Event_Flags
{
   EVAS_EVENT_FLAG_NONE = 0, /**< No fancy flags set */
   EVAS_EVENT_FLAG_ON_HOLD = (1 << 0), /**< The event is being delivered but should be put "on hold" until the on hold flag is unset \n
   The event should be used for informational purposes and maybe for some indications visually, but should not actually perform anything. */
   EVAS_EVENT_FLAG_ON_SCROLL = (1 << 1) /**< The event occurs while scrolling \n
   For example, DOWN event occurs during scrolling; the event should be used for informational purposes and maybe for some indications visually, but should not actually perform anything. */
} Evas_Event_Flags; /**< Flags for Events */


Evas 단에서 이미 ON_HOLD에 대한 enum을 정의하고 있습니다.

EVAS_EVENT_FLAG_ON_HOLD가 설정된 객체(Evas_Object 혹은 group 혹은 part)는,

타 객체에서 설정한 EVAS_EVENT_FLAG_ON_HOLD에 의해 이벤트를 받지 못합니다.


elementary 소스를 뒤져보면,

대부분의 위젯들이 event를 처리하는 콜백에서 해당 객체가 ON_HOLD 처리가 되어 있는지 확인하고,

ON_HOLD가 set되어 있으면 event 콜백 진입 직후에 return 해버립니다.


Repository : git://review.tizen.org/framework/uifw/elementary

Branch : tizen_2.4

./elm_video.c:31:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_flip.c:1541:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_flip.c:1572:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_flip.c:1634:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_scroller.c:127:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elc_popup.c:2148:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_flipselector.c:440:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_slideshow.c:34:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elc_multibuttonentry.c:80:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_gengrid.c:852:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_gengrid.c:2312:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_diskselector.c:913:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_web.c:180:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_entry.c:3208:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_button.c:297:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_interface_scrollable.c:2143:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_map.c:4378:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_panel.c:818:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elc_ctxpopup.c:1198:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_genlist.c:2682:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_genlist.c:3547:   //if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elm_radio.c:164:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./els_scroller.c:1863:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return ;
./els_scroller.c:1931:   //   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return ;
./els_scroller.c:2193:   //   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return ;
./els_scroller.c:2512:   //   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return ;
./elm_list.c:426:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_colorselector.c:1760:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_slider.c:381:   if (mev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_slider.c:391:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elc_naviframe.c:1332:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
./elc_naviframe.c:1773:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_check.c:155:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elc_player.c:51:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_toolbar.c:756:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;
./elm_photocam.c:944:   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return EINA_FALSE;


스크롤러 등에 삽입되어 이벤트를 유연하게 처리하고자 한다면,

위의 위젯들처럼 ON_HOLD가 설정된 event에는 return 처리를 해야겠지요.


그렇다면, 최초 발생한 이벤트는 어떻게 되는 것일까요?

ON_HOLD된 파트들은 건너뛰고,

계속 부모 객체를 찾아갑니다.

ON_HOLD가 설정되지 않은 부모 객체까지 타고 올라가서 이벤트를 처리하게 합니다.

그와 관련된 부분은 아래 propagate 함수에서 엿보실 수 있습니다.


Repository : git://review.tizen.org/framework/uifw/elementary

Branch : tizen_2.4

src/lib/elm_widget.c

EAPI Eina_Bool
elm_widget_event_propagate(Evas_Object *obj,
                           Evas_Callback_Type type,
                           void *event_info,
                           Evas_Event_Flags *event_flags)
{
   API_ENTRY return EINA_FALSE; //TODO reduce.

   if (!_elm_widget_is(obj)) return EINA_FALSE;
   Evas_Object *parent = obj;
   Elm_Event_Cb_Data *ecd;
   Eina_List *l, *l_prev;

   while (parent &&
          (!(event_flags && ((*event_flags) & EVAS_EVENT_FLAG_ON_HOLD))))
     {   
        sd = evas_object_smart_data_get(parent);
        if ((!sd) || (!_elm_widget_is(obj)))
          return EINA_FALSE;  //Not Elm Widget
        if (!sd->api) return EINA_FALSE;

        if (sd->api->event(parent, obj, type, event_info))
          return EINA_TRUE;

        EINA_LIST_FOREACH_SAFE(sd->event_cb, l, l_prev, ecd)
          {https://docs.enlightenment.org/auto/edje/edcref.html
             if (ecd->func((void *)ecd->data, parent, obj, type, event_info) ||
                 (event_flags && ((*event_flags) & EVAS_EVENT_FLAG_ON_HOLD)))
               return EINA_TRUE;
          }
        parent = sd->parent_obj;
     }   

   return EINA_FALSE;
}


이상입니다.

다음 포스팅에서도 part의 기본필드를 알아보도록 하겠습니다.


그럼 좋은 하루 보내세요~

끝_


* References

https://docs.enlightenment.org/auto/edje/edcref.html


part 블록에서는 자칫 정신을 잃으면, 급류에 휩쓸려 내려가 죽습니다;

워낙에 많은 복병과 암초가 기다리고 있기 때문에,

가급적이면 많은 예외사항을 다뤄보도록 하겠습니다.

만약 누락된 것이 있다면 댓글로 남겨주세요.

힘 닿는 만큼 추가해보도록 하겠습니다.



안녕하세요, Tizen 개발자 윤진입니다.


part는 parts 블록 안에 위치합니다.

parts 블록 안에는 1개 이상의 part를 배치할 수 있습니다.

parts 안에는 part 말고 다른 블록은 없지요.


parts 안에 쌓이는 part의 순서에도 의미가 있습니다.

위에 있는 part 일수록 레이어는 가장 아래에 위치합니다.

하나씩 part를 만들때 가장 상단에 있는 part부터 차곡차곡 쌓는다고 생각하면 됩니다.



개별적인 part는 완결된 화면 구성 단위인 group 내에 위치한 구성 '요소'입니다.

뒤집에 말하면, 각각의 구성요소가 모여서 하나의 의미있는 group이 되지요.


group {
   parts {
      part{} /* 최하단 레이어 */
      part{}
      part{}
      part{}
      part{}
      part{} /* 최상단 레이어 */
   }
}


part는 선이나 사각형이 될 수 있고,

이미지나 텍스트가 될 수도 있습니다.

버튼과 같은 다른 group을 통째로 넣을 수도 있습니다.

각각의 쓰임에 따라 syntax가 조금씩 달라집니다.


group {
   parts {
      ...
      part {
         name: "part_name";
         type: IMAGE;
         mouse_events:  1;
         repeat_events: 0;
         ignore_flags: NONE;
         clip_to: "another_part";
         source:  "group_name";
         pointer_mode: AUTOGRAB;
         description { }
         dragable { }
         items { }
      }
      ...
   }
}


part 내에서 사용하는 기본적인 필드를 우선 살펴보겠습니다.

여기서 언급하는 필드로 파트의 속성을 엿볼 수 있습니다.

필드의 세부적인 속성값이나 여기에 언급하지 않은 필드도 다른 포스팅에서 다룰 예정입니다.

한 번에 모두를 다루기에는 그 양이 너무 많네요.


- name: [part_name];

   파트의 이름을 지정할 수 있습니다.

   파트 이름은 program에서 애니메이션 효과를 줄 때 사용할 수 있습니다.

   c코드에서 파트이름으로 파트에 직접 접근할 수도 있습니다만 일반적인 용례는 아닙니다.


   어쨌든 외부에서 접근할 수 있으니,

   다른 파트 이름과 구별되는 고유의 이름을 지어주어야 합니다.

   파트이름은 큰 따옴표로 감싸주세요.



- type: [TYPE];

   타입은 아래처럼 확정된 값을 따옴표 없이 대문자로 넣을 수 있습니다.

   만약 아무런 값도 지정하지 않았다면 default로 IMAGE가 설정됩니다.

•    TEXT : 싱글라인 텍스트
•    TEXTBLOCK : 멀티라인/스타일이 가능한 텍스트블록
•    RECT : 사각형
•    IMAGE : 이미지
•    SWALLOW : group을 통째로 탑재할 수 있는 SWALLOW 타입(c에서 확정)
•    GROUP : group을 통째로 탑재할 수 있는 GROUP 타입(edc에서 확정)

•    BOX : 박스에 다른 그룹을 차곡차곡 삽입
•    TABLE : 테이블

•    EXTERNAL : 익스터널 오브젝트
•    PROXY : 파트 컨텐트 재사용(속도 최적화)
•    SPACER : 파트 위치+크기 지정(메모리 최적화)



- mouse_events:  [1 or 0];

   이름은 마우스 이벤트이지만 파트영역에서 발생하는 모든 이벤트를 통제할 수 있습니다.

   설정할 수 있는 값은 True를 뜻하는 1과 False를 뜻하는 0입니다.

   True를 설정하면 해당 파트가 '이벤트를 받을 수 있는 상황에서' 이벤트를 받게 됩니다.


   이벤트를 받을 수 있는 상황이란,

   - 파트영역이 화면에 보여야하고,

   - 파트영역을 덮는 다른 파트가 있으면 안됩니다.


   흔히 저지르는 실수 중에 하나가 투명한 파트를 상위 레이어에 만들어 두고,

   그 아래에 있는 파트에서 이벤트를 기다리는 것이지요.

   아무리 기다려도 위에 있는 레이어가 낚아채버리면 이벤트는 오지 않습니다.

   가장 기본적인 원칙이지요.


   만약 0으로 설정하면 어떤 이벤트도 받지 않습니다.

   이벤트를 받을 수 있는 상황인데 이벤트를 받지 않으면,

   그 하위에 있는 레이어에게 기회가 돌아가게 됩니다.

   역으로 자기보다 상단에 레이어가 있지만,

   상단 레이어가 mouse_events를 0으로 설정하면 자기가 이벤트를 받을 수 있습니다.

   default 값은 이벤트를 처리한다는 뜻으로 1로 설정하지요.



- repeat_events: [1 or 0];

   위에서도 언급하였지만 이벤트는 레이어의 높낮이에 영향을 받습니다.

   최상위 레이어부터 최하위 레이어까지 이벤트가 순서대로 전달될 수 있습니다.

   만약 상위 레이어가 이벤트를 처리하였지만, 하위레이어도 이벤트를 받아야하는 경우도 있을 수 있습니다.

   그럴 때 repeat_events를 사용하여 이벤트를 하위 레이어에 전달할 수도 있습니다.

   default는 0이기 때문에 이벤트를 받은 레이어 아래에 이벤트를 전달하지 않습니다.



- scale: [1 or 0];

   scale은 매우 중요한 개념이지만 그 중요도만큼이나 까다로운 필드입니다.

   scale 값을 True인 1로 설정하면, scale factor의 영향을 받는다는 의미이고,

   False인 0으로 설정하면, scale factor의 영향을 받지 않는다는 의미입니다.


   scale factor는 무엇일까요?

   scale factor는 double 형의 상수입니다.

   해상도와 화면사이즈가 제각각인 디스플레이에서,

   육안으로 봤을때 유사한 크기로 교정하기 위해 플랫폼에서 제공하는 계산된 인자입니다.


   예를 들어 동일한 해상도의 1인치/3인치 디스플레이가 있다고 합시다.

   1인치 화면에서 1cm 너비로 보이는 파트영역을,

   3인치 화면에서 3cm가 아닌 여전히 1cm로 너비를 지정하고 싶을 경우,

   1인치에서 1로 scale factor를 지정하였다면,

   3인치에서는 1/3로 scale factor를 지정합니다.

   그러면 1인치에서는 1cm x 1scale factor = 1cm로 설정이 되고,

   3인치에서는 3cm x 1/3scale factor = 1cm로 설정이 되겠지요.


   굉장히 간단하게 설명을 드렸지만, 대강의 의미는 전달되었을거라 생각합니다.

   차후에 scale factor에 대한 이야기를 제대로 할 기회가 있겠지요.


   이러한 scale factor에 영향을 받는 값은,

   min, max 그리고 font size입니다.

   위의 세값을 설정하지 않은 파트는 scale을 1로 지정하여도 scale의 영향을 받지 않습니다.

   반대로 min, max, font size를 지정한 파트에 scale 값을 0으로 줘버리면,

   scale factor의 영향을 받지 않고 지정한 사이즈 그대로 화면에 보여집니다.

  

   default값은 1.0입니다.

   스케일 값을 사용하는 것이 default이지요.



- pointer_mode: [MODE];

   파트영역 내에서 시작하였지만, 파트영역 밖에서 끝난 이벤트를 처리하는 방식을 지정할 수 있습니다.

   mouse down과 mouse up 이벤트로 설명하는 것이 빠르겠네요.

   파트영역 내에서 mouse down을 하였지만,

   mouse를 move하여 파트영역 밖으로 빠져나간 후 mouse up을 할 수 있습니다.

   그렇게 되면 mouse up이벤트를 파트영역이 받아야 할까요?

   그에 대한 선택을 사용자가 pointer_mode로 할 수 있습니다.


   AUTOGRAB 값을 주면,

   mouse up 이벤트가 파트영역 외부에서 이뤄지더라도 mouse down이 발생한 파트가 이벤트를 받습니다.


   NOGRAB 값을 주면,

   mouse up 이벤트가 파트영역 외부에서 이뤄지면 mouse down이 발생한 파트로는 이벤트가 오지 않습니다.


   default는 AUTOGRAB이기 때문에 NOGRAB을 사용하지 않는 이상,

   pointer_mode를 굳이 사용할 필요는 없습니다.



- precise_is_inside: [1 or 0];

   투명한 영역을 가지고 있는 이미지에서 빛을 발하는 '훌륭한' 기능입니다.

   이미지가 이벤트를 받는 경우,

   이미지의 투명한 영역은 이벤트를 받지 않고,

   불투명한 영역에서만 이벤트를 받고 싶을 수도 있습니다.

   바로 그럴 경우, precise_is_inside를 1로 지정하면 됩니다.

   그러면 투명한 영역은 파트영역 내부(inside)로 포함되지 않습니다.


   default 값은 0입니다.

   투명한 영역도 이미지의 영역이므로 이벤트를 모두 처리합니다.


오늘은 여기까지 하겠습니다.

part가 가지는 다른 무수한 기능은 다음 포스팅에서 하나씩 살펴보겠습니다.


그럼 좋은 하루 보내세요~

끝_


edc에서 group은 비교적 군더더기 없이 만들어져 있습니다.

group을 사용할 때, '충돌'이나 '이해할 수 없는 동작'을 고민할 필요가 없습니다.


group과 얽힌 재미난 에러를 하나쯤 언급하기 위해 기억을 더듬고 있지만,

도통 떠오르지 않네요.

비교적 간단한 역할을 담당하고 있으니 실구현에도 어려움은 없는 녀석이었나봅니다.

여러분도 그럴 것이라 믿어의심치 않습니다. :)



안녕하세요, Tizen 개발자 윤진입니다.


edc를 작성할 때 group은 하나의 완결된 구성이 됩니다.

c파일에서 elm_layout_add(), elm_layout_file_set() 콤보로 가져다 쓰는 단위가 group입니다.

하나의 collections에 수많은 group이 있다면,

c에서 필요한 group만큼 elm_layout_add()를 불러서 사용하면 됩니다.


그렇다면 완결된 구성을 위해서는 그 안에 어떤 요소를 담고 있어야 할까요?

그룹이 가진 많은 요소 중에 가장 중요한 요소는 parts와 programs 두가지입니다.

parts는 화면을 구성하는 part의 상위 블록이고,

programs는 part에 움직임을 부여할 수 있는 program의 상위 블록이지요.


collections {
   group {
      parts {
         part{}
         part{}
         part{}
         part{}
      }
      programs {
         program{}
         program{}
         program{}
         program{}
      }
   }


지난
collections 포스팅에서도 언급한 블록도에서 programs를 추가해봤습니다.

위의 블록도처럼 group에 parts와 programs만 있으면 화면에 무언가 그려낼 수 있습니다.

parts와 programs 블록은 각각 며칠씩 공을 들여 설명해보도록 하겠습니다.


group에는 parts와 programs 외에 다른 요소들도 있습니다.

name, inherit, min, max.

위의 요소들은 parts와 programs에 비하면 아주 간단하게 설명할 수 있습니다.


- name: [group name];

   그룹의 이름을 지정합니다.

   위에서 잠깐 언급한 elm_layout_file_set()에서 세번째 인자가 그룹 name입니다.

   c파일에서는 group이 있는 파일명과 여기서 지정한 그룹명으로 그룹에 접근할 수 있습니다.

   만약 group 이름이 동일하다면 새로운 group이 이전 group을 완전히 override해버립니다.

   edc에서 group override를 의미있게 사용하는 것을 보질 못해서,

   이 부분은 컴파일을 위한 편의가 아닐까 추정하고 있습니다.

   어쨌든, override되지 않게 그리고 문맥에 맞게 사용하려면 group명을 잘 지어주세요.

   언제나 모든 것은 적절한 네이밍에서 시작하지요.


- min: [width] [height];

   min의 의미는 "최소한 이 사이즈를 확보해주세요"입니다.

   group이 어디에 포함되어 들어가든 min 사이즈만큼은 확보가 됩니다.

   min에서 지정한 사이즈보다 group이 작아질 수는 없습니다.

   원칙적으로 그렇습니다.

   min에서는 그룹의 높이와 너비를 지정하겠지요.

   그룹의 높이/너비가 확정되어 있고 '절대로 변할 일이 없다'면,

   group 차원에서 정의해도 무방합니다.

   하지만, group이 상태에 따라 사이즈가 바뀐다면,

   여기서 min을 사용하면 안됩니다.

   min은 mandatory가 아니기에 상황에 따라 사용하면 됩니다.

   참고로 전 group에서 min을 사용하지 않습니다.

   min은 다른 곳에서 더 재미있는 형태로 사용할 수 있습니다.


- max: [width] [height];

   max는 min과는 반대의 개념입니다.

   "최대한 이 사이즈를 넘지 않도록 해주세요" 입니다.

   max의 중요성은 min에 비해 상당히 떨어지는데요,

   각각의 요소들이 사이즈를 잡을때 min을 기준으로 잡기 때문입니다.

   max는 느슨한 가이드라인이지요.

   그렇다고 사용하지 않는다는 것은 절대 아닙니다.

   차후에 min/max에 대해서는 계속 설명할 기회가 있을 겁니다.

   어쨌든 그룹이 max 값을 넘지 않게 합니다.

   min처럼 mandatory가 아니기 때문에 필요에 의해 사용하면 됩니다.


- alias: [additional group name];

   alias에서 그룹에 별칭을 지정할 수 있습니다.

   하나의 그룹이 다수의 이름을 가질 필요가 있을때 사용합니다.

   그룹을 하나 만들었는데 여러 군데에서 사용할 수 있고,

   각각에 고유한 네이밍을 하길 원할 때가 있습니다.

   그 때 alias는 의미있는 선택이 될 수 있습니다.


group의 모든 요소를 골고루 사용하는 define문을 icon.edc에서 찾았습니다.

Repository : git://review.tizen.org/framework/uifw/elementary

Branch : tizen_2.3

File : data/themes/widgets/icon.edc


#define GROUP_ALIAS_ICON(Name, Alias, File, Min, Max)                   \
   group { name: "elm/icon/"##Name##"/default"; min: Min Min; max: Max Max; \
      images.image: File COMP; parts { part { name: "base";             \
            description { aspect: 1.0 1.0; aspect_preference: BOTH;     \
               image.normal: File; } } } }
/*** 생략 ***/
GROUP_ALIAS_ICON("home", "toolbar/home", "icon_home.png", 32, 0);
GROUP_ALIAS_ICON("close", "toolbar/close", "icon_close.png", 32, 0);
GROUP_ALIAS_ICON("apps", "toolbar/apps", "icon_apps.png", 32, 0);
GROUP_ALIAS_ICON("arrow_up", "toolbar/arrow_up", "icon_arrow_up.png", 32, 0);
GROUP_ALIAS_ICON("arrow_left", "toolbar/arrow_left", "icon_arrow_left.png", 32, 0);
GROUP_ALIAS_ICON("arrow_right", "toolbar/arrow_right", "icon_arrow_right.png", 32, 0);
GROUP_ALIAS_ICON("chat", "toolbar/chat", "icon_chat.png", 32, 0);
GROUP_ALIAS_ICON("clock", "toolbar/clock", "icon_clock.png", 32, 0);
GROUP_ALIAS_ICON("delete", "toolbar/delete", "icon_delete.png", 32, 0);
GROUP_ALIAS_ICON("edit", "toolbar/edit", "icon_edit.png", 32, 0);
GROUP_ALIAS_ICON("refresh", "toolbar/refresh", "icon_refresh.png", 32, 0);
GROUP_ALIAS_ICON("folder", "toolbar/folder", "icon_folder.png", 32, 0);
GROUP_ALIAS_ICON("file", "toolbar/file", "icon_file.png", 32, 0);
GROUP_ALIAS_ICON("no_photo", "photo/no_photo", "head.png", 32, 0);


위에서 난데없는 #define이 나와서 당황하셨을 지도 모르겠군요.

C언어에서처럼 #define을 유사하게 사용할 수 있습니다.

우리가 살펴봐야할 부분은 GROUP_ALIAS_ICON의 Name, Alias, Min, Max입니다.

실제로 #define이 사용되는 부분을 보면,

Name, Alias, Min값을 주로 사용하고 있군요.

Max는 마지막 인자로 '0'을 넣어주고 있네요.

최대 크기가 0이라는 의미는 아니고 Max를 사용하지 않겠다는 의미입니다.

이 예시에서는 그룹의 Name과 Alias를 지정하고,

최소/최대 사이즈를 지정하였습니다.


group에 대한 설명은 이것으로 간단하게 마치겠습니다.

다음 시간에는 part에 대해서 살펴보도록 하겠습니다.


그럼 좋은 하루 보내세요~

끝_



EFL에 대해 블로깅을 하는 날이 오고야 말았습니다.

오늘부터 n일 동안 edc에 대해서 하나씩 살펴보도록 하겠습니다.


진도를 빨리 뺀다는 마음보다는,

다소 느리더라도 천천히 하나하나 맛을 음미하며 진행하고 싶네요.


하지만, 충분히 설명해낼 수 있을지는 확신이 안 서네요.



안녕하세요, Tizen 개발자 윤진입니다.


edc는 EFL의 백미라 할 수 있습니다.

(써 놓고 보니 '백미' 타이틀은 ecore나 evas에게 주어야하지 않나라는 생각도 듭니다;)

edc 스크립트를 사용하여 레이아웃을 잡고 이미지를 배치하고 텍스트를 심습니다.

이러한 정적인 배치는 edc의 part를 이용하여 구성할 수 있습니다.


위에 나열한 정적인 요소에 program을 통해 생명력을 불어넣을 수 있습니다.

part 하나 하나가 program의 손길로 동적인 효과를 가집니다.

네다섯줄의 스크립팅으로 부드러운 애니메이션을 탑재할 수 있습니다.


edc 스크립트에서 가장 처음 마주하게 되는 블록이 collections입니다.

collections에는 하나의 유의미한 단위로 동작하는 group이 들어갑니다.

group 화면을 구성하는 완결된 하나의 집합이고,

collections는 그러한 group을 1개 이상 가집니다.


예를 들어 버튼+텍스트의 집합을 하나의 완결된 'button' group이 있다고 하죠.

경우에 따라서 버튼의 모양은 천양지차로 다를 수 있습니다.

네모난 버튼이 일반적이긴 하지만, 사각이 라운딩 처리된 라운딩 버튼이 있을 수도 있습니다.

이럴 경우 네모난 버튼용 group과 라운딩 버튼용 group이 따로 둘 수 있습니다.

세모난 버튼용 group이나 투명 버튼용 group도 있을 수 있겠네요.

생김새는 조금씩 다르지만 버튼이라는 기능적인 유사성이 있기 때문에,

하나의 collections로 모아서 관리할 수 있겠죠.

이처럼 collections는 다양한 theme을 묶어주는 역할을 할 수 있습니다.


그렇다면 group이 하나만 있어도 collections로 묶어줘야 할까요?

네, 맞습니다.

collections는 group이 1개 이상 있는 경우에 반드시 써주어야 합니다.

collections로 감싸지지 않은 group은 제대로 컴파일되지 않습니다.

collections - group - parts - part

혹은

Collections - group - programs - program

위에 이르는 블록 구조에서 최상단에는 언제나 collections가 있습니다.


edc 파일은 collections를 반드시 갖지만,

두 개 이상 가질 수는 없습니다.

edc 파일에는 오직 하나의 collections만 사용할 수 있습니다.


collections {
   group {
      parts {
         part{}
         part{}
         part{}
         part{}
      }
   }


EFL elementary repository에서 사용하고 있는 edc 파일을 예제로 살펴보겠습니다.

Repository : git://review.tizen.org/framework/uifw/elementary

Branch : tizen_2.3

File : data/themes/default.edc

collections {
///////////////////////////////////////////////////////////////////////////////
   group { name: "elm/focus_highlight/top/default";
   /* ... 생략 ... */
      parts {
         part { name: "base";
            type: RECT;
            repeat_events: 1;
            description { state: "default" 0.0;
               rel1.relative: 0.0 0.0;
               rel2.relative: 1.0 1.0;
               visible: 0;
            }
         }
         part { name: "shine";
            type: IMAGE;
            mouse_events: 1;
            repeat_events: 1;
            ignore_flags: ON_HOLD;
            description { state: "default" 0.0;
               image {
                  normal: "emo-unhappy.png";
               }
               rel1.to: "base";
               rel1.relative: 1.0 0.0;
               rel1.offset: -15 -15;
               rel2.to: "base";
               rel2.relative: 1.0 0.0;
               rel2.offset: 14 14;
            }
            description { state: "disabled" 0.0;
               inherit:  "default" 0.0;
               color: 0 0 0 0;
            }
         }
   /* ... 생략 ... */
///////////////////////////////////////////////////////////////////////////////
   group { name: "elm/focus_highlight/bottom/default";
      parts {
         part { name: "shine";
            type: RECT;
            mouse_events: 1;
            repeat_events: 1;
            ignore_flags: ON_HOLD;
            description { state: "default" 0.0;
               color: 0 255 0 50;
               rel1.offset: 0 0;
               rel2.offset: 0 0;
            }
            description { state: "disabled" 0.0;
               inherit:  "default" 0.0;
               color: 0 0 0 0;
            }
         } 
   /* ... 생략 ... */
///////////////////////////////////////////////////////////////////////////////
   group { name: "elm/access/base/default";
      images {
         image: "access_glow.png" LOSSY 85;
      }
      parts {
         part { name: "block1"; type: RECT;
            mouse_events:  0;
            description { state: "default" 0.0;
               color: 0 0 0 200;
               rel1 { to: "base";
                  relative: 0.0 0.0;
                  offset: -100000 -100000;
               }
               rel2 { to: "base";
                  relative: 1.0 0.0;
                  offset: 100000 96;
               }
            }
         }
         part { name: "block2"; type: RECT;
            mouse_events:  0;
            description { state: "default" 0.0;
               color: 0 0 0 200;
               rel1 { to: "base";
                  relative: 0.0 1.0;
                  offset: -100000 -97;
               }
               rel2 { to: "base";
                  relative: 1.0 1.0;
                  offset: 100000 100000;
               }
            }
         }
         part { name: "block3"; type: RECT;
            mouse_events:  0;
            description { state: "default" 0.0;
               color: 0 0 0 200;
               rel1 { to: "block1";
                  relative: 0.0 1.0;
                  offset: 0 0;
               }
               rel2 { to_x: "base"; to_y: "block2";
                  relative: 0.0 0.0;
                  offset: 96 -1;
               }
            }
         }
         part { name: "block4"; type: RECT;
            mouse_events:  0;
            description { state: "default" 0.0;
               color: 0 0 0 200;
               rel1 { to_x: "base"; to_y: "block1";
                  relative: 1.0 1.0;
                  offset: -97 0;
               }
               rel2 { to: "block2";
                  relative: 1.0 0.0;
                  offset: -1 -1;
               }
            }
         }
         part { name: "base";
            mouse_events:  0;
            description { state: "default" 0.0;
               image.normal: "access_glow.png";
               image.border: 112 112 112 112;
               rel1.offset: -102 -102;
               rel2.offset:  101  101;
               fill.smooth: 0;
            }
         }
      }
   }


위에서 보시다시피 하나의 collections 아래에 다수의 group이 놓여져 있습니다.

focus와 highlight와 관련된 기능이 group으로 구현되어 있네요.


group이 더욱 많이지면 collections에다가 제대로 붙이기 힘들 정도로 길어질 수도 있습니다.

이럴 때는 collections 문 안에서 #include 문을 사용하여 group을 include할 수 있습니다.

같은 파일의 아랫 부분을 보면, #include 문을 사용했습니다.


collections {
   /* ... 생략 ... */
#include "widgets/bg.edc"
#include "widgets/border.edc"
#include "widgets/scroller.edc"
#include "widgets/label.edc"
#include "widgets/button.edc"
#include "widgets/clock.edc"
#include "widgets/datetime.edc"
#include "widgets/dayselector.edc"
#include "widgets/menu.edc"
#include "widgets/frame.edc"
#include "widgets/tooltip.edc"
#include "widgets/hover.edc"
#include "widgets/ctxpopup.edc"
#include "widgets/entry.edc"
#include "widgets/bubble.edc"
#include "widgets/photo.edc"
#include "widgets/thumb.edc"
#include "widgets/icon.edc"
#include "widgets/toolbar.edc"
#include "widgets/notify.edc"
#include "widgets/slideshow.edc"
#include "widgets/win.edc"
#include "widgets/list.edc"
#include "widgets/slider.edc"
#include "widgets/actionslider.edc"
#include "widgets/genlist.edc"
#include "widgets/check.edc"
#include "widgets/radio.edc"
#include "widgets/progressbar.edc"
#include "widgets/separator.edc"
#include "widgets/spinner.edc"
#include "widgets/index.edc"
#include "widgets/gengrid.edc"
#include "widgets/photocam.edc"
#include "widgets/map.edc"
#include "widgets/panes.edc"
#include "widgets/panel.edc"
#include "widgets/conformant.edc"
#include "widgets/calendar.edc"
#include "widgets/colorselector.edc"
#include "widgets/flipselector.edc"
#include "widgets/diskselector.edc"
#include "widgets/fileselector.edc"
#include "widgets/layout.edc"
#include "widgets/segment_control.edc"
#include "widgets/player.edc"
#include "widgets/video.edc"
#include "widgets/naviframe.edc"
#include "widgets/multibuttonentry.edc"
#include "widgets/popup.edc"
#include "ews.edc"
#include "widgets/pointer.edc"
}


위에서 사용한 #include 문은 현재 파일이 있는 위치를 기준으로 상대경로를 사용했습니다.

widgets 디렉토리 아래에 있는 수많은 edc 파일을 include하였지요.

edc 파일들을 하나씩 열어보면 모두 group인 것을 알 수 있습니다.

collections를 품고 있는 edc는 단 하나도 없습니다.

왜냐하면 이미 collections로 감싸졌기 때문입니다.


위의 예에서는 모든 위젯을 하나의 collections에서 관리하고 있습니다.

- 이처럼, 하나의 프로젝트 단위에서는 하나의 collections를 사용할 수 있습니다.

- 혹은, 각각의 위젯을 하나의 collections 단위로 쪼갤 수도 있겠지요.

관리상 용이한 것을 따르시면 됩니다.

꼭 정해진 것은 없습니다.


오늘은 collections에 대해서 간단하게 살펴보았습니다.

다음 시간에는 group에 대해서 훑어보도록 하겠습니다.


그럼 좋은 하루 보내세요~

끝_

2015년 7월 22일,

Tizen2 Seller Office에서 메일이 하나 왔습니다.



인도, 방글라데시, 스리랑카에 이어 네팔에서도 유료판매가 가능하다는 메일입니다.

타이젠이 인도를 중심으로 그 주변국가로 영역을 지속적으로 확장하고 있습니다.



7월 말 인도에서 열리는 Tizen Developer Summit으로,

남부아시아에서 타이젠의 존재를 다시 한 번 입증하겠지요.



그리고 올 9월 2015 Tizen Developer Conference는 중국에서 열리는 만큼,

점차 타이젠의 영역이 넓어지지 않을까 조심스럽게 생각해봅니다.



182개국 중 4개국에서 유료앱 판매가 가능하게 되었으니,

이제 남은 178개국까지 유료앱 판매가 확대되길 기대해봅니다


그럼 좋은 하루 보내세요.

끝_

타이젠 네이티브 앱을 개발하려면 화면 구성을 해야겠지요.

Enventor가 화면을 구성할 때 좋은 친구가 되어줄 것입니다.


Enventor를 사용하여 레이아웃에 이미지를 넣어봅시다.

Enventor에 이미지를 위한 공간을 마련하는 것은 어렵지 않은데요,

이미지를 위한 공간에 이미지를 포함시키려면 이미지파일은 어디에 두어야 할까요?



안녕하세요, Tizen 개발자 윤진입니다.


Tizen 2.3 SDK에서 edc 파일을 더블클릭하면 Enventor가 런칭됩니다.

Enventor에 이미지를 저장하기 위한 파트를 하나 만들어둡니다.

그리고 파트에 이미지를 하나 지정합니다.

이미지 파일은 어디에 두는게 좋을까요?

Enventor > Setting > Image Paths

위의 경로를 변경하여 이미지를 저장해봅니다.



Image Paths를 이미지 파일이 있는 디렉토리로 변경하고,

Enventor의 preview 영역을 확인하니 이미지가 제대로 나옵니다.


자, 그리고 컴파일을 시도하면 위의 edc 파일을 빌드할 수 없다고 에러가 나옵니다.

무엇이 문제인 것일까요?


Eventor와 Tizen SDK가 완벽하게 통합되지 않았습니다.

위에서 설정한 Image Paths는 Enventor 만을 위한 메뉴입니다.

Tizen SDK에서 빌드를 위한 Image Paths도 지정해주어야 합니다.


Project Explorer > (마우스 우클릭) > Properties

위처럼 진입하여 메뉴윈도우를 띄웁니다.

C/C++ Build > Settings > Tool Settings > EDC Resource Compiler > misc

위의 메뉴까지 진입하면 Image Path를 입력할 수 있는 메뉴가 나옵니다.



Image Path에 있는 "+" 버튼을 눌러서 이미지 파일이 있는 위치를 추가해줍니다.

그리고 OK를 눌러 다시 빌드를 해봅니다.

이제 빌드에 성공하였네요.


그럼 좋은 하루 보내세요~

끝_


최신 버전 Ubuntu 15.04 64비트에 Tizen 2.3 SDK를 설치해보았습니다.

올초에 나온 Tizen 2.3 SDK는 14.04까지 공식지원하고 있습니다.

다행히 15.04에서도 SDK의 기본 기능을 사용하는데 문제는 없습니다.


하지만, 드디어 에러를 만나게 됩니다.

Tizen SDK 내에 edc 파일을 편집할 때 사용하는 enventor라는 툴이 실행이 안되더군요.

이번 포스팅은 enventor를 '임시'로 사용할 수 있는 방법을 공유합니다.



안녕하세요, Tizen 개발자 윤진입니다.


이번 포스팅은 타이젠 2.3 SDK에서 공식지원하지 않는 Ubuntu 15.04 환경에서 벌어진 일을 다룹니다.

Ubuntu 14.04에서는 위와 같은 에러 없이 Eventor를 사용하실 수 있습니다.

하지만, 최신 버전의 우분투에서 타이젠 SDK를 사용하고 싶으시다면 이번 포스팅을 참고해주세요.



SDK에서 edc 파일을 편집하기 위해 파일을 더블클릭하면 위와 같은 팝업창이 뜹니다.

팝업 내용은 edc 편집툴인 enventor를 런칭하지 못한다는 내용인데요,

에러로그 뷰를 살펴보라고 하지만, 에러로그 뷰에는 별 다른 내용이 없습니다.

그래서 이를 해결하기 위해서 enventor를 커맨트창에서 직접 실행해보았습니다.


$ ~/tizen-sdk/tools/enventor/bin/enventor 
/home/storycompiler/tizen-sdk/tools/enventor/bin/enventor: error while loading shared libraries: libelementary.so.1: cannot open shared object file: No such file or directory


elementary 라이브러리가 없어서 enventor가 실행이 안되는 것이었군요.

하지만, 타이젠 SDK에는 elementary 라이브러리가 포함되어 있습니다.


$ ls ~/tizen-sdk/tools/efl-tools/lib/libelementary.so.1
/home/storycompiler/tizen-sdk/tools/efl-tools/lib/libelementary.so.1

위의 위치를 보시면 EFL에서 제공하는 다수의 라이브러리를 확인하실 수 있습니다.

물론 elementary도 찾을 수 있지요.

라이브러리가 있는데 찾지 못하는 것은 라이브러리 위치가 등록되어있지 않기 때문입니다.

타이젠 라이브러리 디렉토리를 등록하기 위해 /etc/ld.so.conf.d 디렉토리에 tizen.conf 파일을 새로 만듭니다.

그 안에 위의 라이브러리 디렉토리를 추가해둡니다.


$ cat /etc/ld.so.conf.d/tizen.conf
/home/storycompiler/tizen-sdk/tools/efl-tools/lib


그리고 다시 enventor를 실행해봅니다.

이번에는 아래처럼 libgnutls 라이브러리가 없다고 나옵니다.


$ ~/tizen-sdk/tools/enventor/bin/enventor 
/home/storycompiler/tizen-sdk/tools/enventor/bin/enventor: error while loading shared libraries: libgnutls.so.26: cannot open shared object file: No such file or directory


libgnutls.so.26 버전은 여기에서 다운로드 받을 수 있습니다.

위의 사이트가 열리지 않을 경우에 대비하여 64비트 우분투 15.04용 rpm을 첨부합니다.

lib64gnutls26-2.12.14-2-mdv2012.0.x86_64.rpm

아래와 같이 libgnutls.so.26 버전을 설치합니다.

여기에서는 64비트 환경에 맞춰서 64비트용의 라이브러리를 설치하였습니다.


$ sudo rpm -Uvh --force --nodeps lib64gnutls26-2.12.14-2-mdv2012.0.x86_64.rpm
rpm: RPM should not be used directly install RPM packages, use Alien instead!
rpm: However assuming you know what you are doing...
경고: lib64gnutls26-2.12.14-2-mdv2012.0.x86_64.rpm: Header V3 DSA/SHA1 Signature, key ID 26752624: NOKEY
준비 중...                         ################################# [100%]
Updating / installing...
   1:lib64gnutls26-2.12.14-2          ################################# [100%]

그리고 다시 enventor를 실행해봅니다.

이번에는 libgcrypt.so.11 파일이 없다고 나오네요.

$ ~/tizen-sdk/tools/enventor/bin/enventor
/home/storycompiler/tizen-sdk/tools/enventor/bin/enventor: error while loading shared libraries: libgcrypt.so.11: cannot open shared object file: No such file or directory


따라서 이번에도 libgcrypt.so.11 버전을 찾아서 받습니다.

위의 버전은 여기에서 다운로드 받으실 수 있습니다.

위의 사이트가 열리지 않을 경우에 대비하여 64비트 우분투 15.04용 rpm을 첨부합니다.

lib64gcrypt11-1.5.4-5.mga5.x86_64.rpm

$ sudo rpm -Uvh --force --nodeps lib64gcrypt11-1.5.4-5.mga5.x86_64.rpm
rpm: RPM should not be used directly install RPM packages, use Alien instead!
rpm: However assuming you know what you are doing...
경고: lib64gcrypt11-1.5.4-5.mga5.x86_64.rpm: Header V3 RSA/SHA1 Signature, key ID 80420f66: NOKEY
준비 중...                         ################################# [100%]
Updating / installing...
   1:lib64gcrypt11-1.5.4-5.mga5       ################################# [100%]


libgcrypt는 /usr/lib64에 설치가 됩니다.

하지만, 위의 위치는 ld.conf에 등록된 위치가 아닐 수 있습니다.

$ ls /usr/lib64/libgcrypt.so.11
/usr/lib64/libgcrypt.so.11


따라서 좀 전에 elementary를 등록하기 위해 만든 tizen.conf에 lib64도 등록해둡니다.

$ cat /etc/ld.so.conf.d/tizen.conf 
/home/storycompiler/tizen-sdk/tools/efl-tools/lib
/usr/lib64
$ sudo ldconfig


그리고 다시 enventor를 실행하면 이번엔 libtasn1.so.3이 없다는 에러가 나옵니다.

$ ~/tizen-sdk/tools/enventor/bin/enventor 
/home/storycompiler/tizen-sdk/tools/enventor/bin/enventor: error while loading shared libraries: libtasn1.so.3: cannot open shared object file: No such file or directory


이번에도 libtasn1.so.3을 여기에서 다운로드 받습니다.

위의 사이트가 열리지 않을 경우에 대비하여 64비트 우분투 15.04용 rpm을 첨부합니다.

libtasn1-3-1.2-31.2.x86_64.rpm

$ sudo rpm -Uvh --force --nodeps libtasn1-3-1.2-31.2.x86_64.rpm 
rpm: RPM should not be used directly install RPM packages, use Alien instead!
rpm: However assuming you know what you are doing...
경고: libtasn1-3-1.2-31.2.x86_64.rpm: Header V3 DSA/SHA1 Signature, key ID 92fbd4a7: NOKEY
준비 중...                         ################################# [100%]
Updating / installing...
   1:libtasn1-3-1.2-31.2              ################################# [100%]


위의 파일마저 제대로 설치하였다면, 이제는 enventor를 실행할 수 있습니다.


자, 이제 다시 개발의 재미에 빠져보시죠.

그럼 좋은 하루 보내세요~

끝_


* References

http://rpmfind.net/linux/rpm2html/search.php?query=libgnutls.so.26%28%29%2864bit%29

http://rpmfind.net/linux/rpm2html/search.php?query=libgcrypt.so.11%28%29%2864bit%29

http://rpm.pbone.net/index.php3/stat/4/idpl/23618373/dir/redhat_el_5/com/libtasn1-3-1.2-31.2.x86_64.rpm.html


2015년 TDC 타이젠 개발자 회의 일정이 확정되었습니다.

시간 : 9월 17일 ~ 18일

장소 : 중국 광둥성 선전시 푸텐구 쉐라톤 호텔



위치를 보니 역대 TDC 중 가장 가까운 곳에서 열리네요.

홍콩 바로 위가 광둥성 선전시이니 비행기로 3시간 30분 거리입니다.


7월말에 열리는 TDS 타이젠 개발자 모임은 인도 벵갈루루에서 열리고,

9월에 열리는 TDC 타이젠 개발자 회의는 중국 선전시에서 열리는군요.

2015년 타이젠은 북미에서 아시아로 시선을 돌렸습니다.

이왕 아시아를 순회하는 김에 한국에서 다음 제품이 출시되길 기원합니다.

저가 스마트폰 대신 하이엔드 모바일 제품으로 성능을 만천하에 보여주면 좋겠군요.


TDC 2015의 슬로건은,

"타이젠, 모든 것을 연결하는 최고의 방법"

'연결'을 주제로 잡고 있습니다.


위의 키워드로도 흥미진진한 얘기거리가 떠오르네요.

타이젠 공식 사이트에서는 TDC의 세션을 등록을 할 수 있습니다.

7월 3일부터 세션등록을 시작하여 8월 2일까지 받습니다.

이렇게 등록된 세션들은 8월 10일에 스케쥴표와 함께 나타나겠지요.


타이젠 위원회는 크게 아래와 같은 주제를 공모하고 있습니다.

- 타이젠 프로젝트 및 플랫폼 개발

- 앱 개발, 운용, 에코시스템

- 기기 개발, 타이젠 프로파일, IoT


타이젠에 관심있는 분들은 서브미션을 직접 개설하여 연사가 되실 수 있습니다.

혹은 TDC 등록 절차를 통해 서브미션에 청중으로 참여할 수도 있습니다.

7월 인도행보다는 9월 홍콩행이 더욱 구미가 당깁니다.

늦은 여름 휴가를 겸하여 다녀오는 것도 좋겠군요.

https://www.regonline.com/Register/Checkin.aspx?EventID=1732778

위의 사이트에서 등록할 수 있습니다.



현재 환율로 2만 7천여원으로 등록 가능합니다..

타이젠은 에코시스템을 키우기 위해 최대한 저렴한 가격으로 행사를 지원하고 있습니다.

2만 7천원이면... 공짜나 다름없죠.


중국에서 열리는 행사이니만큼 중국어로 진행되겠군요.

영어로 등록할 수 있는 버튼이 있는 것으로 보아 영어권 개발자를 위한 배려도 있겠지요.


세션이 등록되어 확정되면 다시 한 번 소식을 전하도록 하겠습니다.

그럼 좋은 하루 보내세요~


끝_


2015. 7. 1 현재,

타이젠 SDK는 우분투 14.04까지 공식지원합니다.

우분투 최신버전인 15.04는 차기 SDK에서 지원하겠죠.


이미 우분투 15.04를 설치하신 분은,

타이젠 툴을 설치하면서 에러를 마주하실지도 모릅니다.



안녕하세요, Tizen 개발자 윤진입니다.


우분투 15.04를 설치하고 타이젠 gbs 툴을 설치하였습니다.

gbs 설치는 아래 포스팅을 참고하시면 됩니다.

[Tizen] 우분투에 타이젠 플랫폼 툴인 gbs & sdb 설치해보기

[Tizen] 타이젠 gbs의 모든 것


하지만 gbs를 설치하고 실행하면 아래와 같은 에러를 마주하실 수 있습니다.

$ gbs
Traceback (most recent call last):
  File "/usr/bin/gbs", line 30, in <module>
    from gitbuildsys import cmd_build
  File "/usr/lib/pymodules/python2.7/gitbuildsys/cmd_build.py", line 33, in <module>
    from gitbuildsys.cmd_export import get_packaging_dir, config_is_true
  File "/usr/lib/pymodules/python2.7/gitbuildsys/cmd_export.py", line 34, in <module>
    from gbp.scripts.buildpackage_rpm import main as gbp_build
ImportError: No module named buildpackage_rpm


이 에러는 buildpackage_rpm 모듈이 없어서 발생합니다.

buildpackage_rpm 모듈은 tizen에서 제공하는 git-buildpackage-rpm(0.6.15 버전)에서 설치됩니다.

하지만, 같은 패키지(0.6.22 버전)가 우분투 15.04에도 정식 탑재되었죠.

안타깝게도 우분투에 설치된 gbp의 버전이 높지만, tizen.org에서 필요로 하는 기능은 없습니다.

따라서 우분투에 설치된 gbp를 downgrade해야합니다.


우분투의 특정 패키지를 downgrade를 하기 위해서는,

과거 한 시점으로 Pinning해서 그 시점의 패키지를 받도록 해야합니다.

간단한 설정파일을 하나 만들어주세요.

$ vi /etc/apt/preferences.d/tizen
Package: *
Pin: origin download.tizen.org
Pin-Priority: 1000

위처럼 만들어 놓으면, 타이젠에서 제공하는 패키지를 우선적으로 설치하게 됩니다.


이제 gbs를 위한 패키지를 설치할 차례입니다.

좀 전에 설정한 pinning을 아래처럼 반영합니다.

$ sudo apt-get update


그리고 필요한 패키지를 아래처럼 설치하면 됩니다.

생각보다 많은 패키지들이 다운그레이드됩니다.

$ sudo apt-get install deltarpm createrepo git-buildpackage-common git-buildpackage-rpm git-buildpackage osc
패키지 목록을 읽는 중입니다... 완료
의존성 트리를 만드는 중입니다      
상태 정보를 읽는 중입니다... 완료
createrepo 패키지는 이미 최신 버전입니다.
createrepo 패키지 수동설치로 지정합니다.
deltarpm 패키지는 이미 최신 버전입니다.
deltarpm 패키지 수동설치로 지정합니다.
git-buildpackage 패키지는 이미 최신 버전입니다.
git-buildpackage-rpm 패키지는 이미 최신 버전입니다.
git-buildpackage-rpm 패키지 수동설치로 지정합니다.
osc 패키지는 이미 최신 버전입니다.
osc 패키지 수동설치로 지정합니다.
다음 패키지가 자동으로 설치되었지만 더 이상 필요하지 않습니다:
  libatk-wrapper-java libatk-wrapper-java-jni libgconf2-4 libgif4 libice-dev
  libpthread-stubs0-dev libsm-dev libx11-dev libx11-doc libxau-dev libxcb1-dev libxdmcp-dev
  libxt-dev python-dateutil python-deltarpm python-keyring python-secretstorage
  x11proto-core-dev x11proto-input-dev x11proto-kb-dev xorg-sgml-doctools xtrans-dev
Use 'apt-get autoremove' to remove them.
제안하는 패키지:
  python-notify zipmerge
다음 새 패키지를 설치할 것입니다:
  git-buildpackage-common
0개 업그레이드, 1개 새로 설치, 0개 제거 및 1개 업그레이드 안 함.
149개를 완전히 설치하지 못했거나 지움.
0 바이트/292 k바이트 아카이브를 받아야 합니다.
이 작업 후 3,944 k바이트의 디스크 공간을 더 사용하게 됩니다.
계속 하시겠습니까? [Y/n] y
경고: 다음 패키지를 인증할 수 없습니다!
  git-buildpackage-common
확인하지 않고 패키지를 설치하시겠습니까? [y/N] y


설치 직후부터 gbs를 사용하실 수 있습니다.

$ gbs
Usage: gbs [GLOBAL-OPTS] SUBCOMMAND [OPTS]
           ...

gbs - the command line tool for Tizen package developers

Global Options:
  -h, --help            show this help message and exit
  -V, --version         show program's version number and exit
  -c CONF, --conf CONF  specify config file for gbs
  -d, --debug           debug output
  -v, --verbose         verbose output

Subcommands:
  changelog (ch)        update the changelog file with the git commit messages
  remotebuild (rb)      remote build package
  devel                 Manage devel branches
  export (ex)           export files and prepare for build
  chroot (chr)          chroot to build root
  import (im)           import spec file/source rpm/tar ball to git repository
  clone (cl)            Clone a git repository
  createimage (cr)      create image using ks file
  submit (sr)           submit tag to gerrit and trigger building in OBS
  pull                  Update a package git repository
  build (lb)            local build package

Try 'gbs SUBCOMMAND --help' for help on a specific subcommand.


그럼 좋은 하루 보내세요~

끝_

데스크탑 PC를 한 대 사서 우분투를 설치하였습니다.

가물가물한 기억을 더듬어서 환경설정을 하고 있습니다.

기본적인 환경설정에만 꼬박 하루가 지나가네요.


사실 윈도우 PC에는 이미 타이젠 SDK가 설치되어 있습니다.

하지만, 윈도우에서는 개발하는 손맛이 나질 않아 우분투에도 타이젠 SDK를 설치하려고 합니다.

타이젠 SDK를 설치하는 과정 자체에는 어려운 것이 전혀 없으나,

타이젠 SDK를 위한 우선 설치해야하는 것들이 있습니다.



안녕하세요, Tizen 개발자 윤진입니다.


만약 타이젠 SDK를 윈도우에 설치하시려면 아래 포스팅을 참고해주세요.

[Tizen] 타이젠 SDK 윈도우에 설치하기, "생각보다 쉽다"


타이젠 SDK는 이클립스를 사용하고 있기 때문에 오라클 JDK를 설치해야합니다.

우분투 환경에서는 open jdk도 받을 수 있으나,

open jdk로는 이클립스를 구동할 수 없습니다.

반드시 오라클에서 배포하는 JDK 받으셔야 이클립스를 제대로 실행할 수 있습니다.


여기를 눌러 오라클 홈페이지에 들어갑니다.

2015. 6. 30 현재 가장 최신은 Java SE Development Kit 8u45 버전입니다.



위의 화면에서 "Accept License Agreement"를 눌러서 라이센스에 동의해주세요.

라이센스에 동의해야만 jdk를 다운로드 받을 수 있습니다.

우분투는 Linux x86(32비트)이나 Linux x64(64비트)를 다운로드 받으셔야 합니다.

여기서는 64비트용 jdk-8u45-linux-x64.tar.gz를 다운로드 받겠습니다.

오라클이 서버를 만드는 회사이니 만큼 다운로드 하나는 확실하게 그리고 빠르게 되네요. :)


다운로드가 완료되었으면 우선 압축부터 풀어야 합니다.

$ tar xvzf jdk-8u45-linux-x64.tar.gz


그리고 압축이 풀린 디렉토리를 통째로 옮깁니다.

$ sudo mkdir -p /usr/lib/jvm/jre1.8.0
$ sudo mv jdk1.8.0_45/* /usr/lib/jvm/jre1.8.0/


자바는 openjdk와 oracle jdk처럼 버전이 다양하기 때문에,

좀 전에 다운로드받은 oracle jdk가 실행될 수 있도록 설정해줍니다.

$ sudo update-alternatives --install <link> <name> <path> <priority>
$ sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jre1.8.0/bin/java 0

곧, /usr/bin/java에 좀 전에 다운로드받은 /usr/lib/jvm/jre1.8.0/bin/java의 링크를 만들어두는 것이지요.


그리고 우분투 파이어폭스에서도 자바를 사용할 수 있도록 자바플러그인을 복사합니다.

$ mkdir ~/.mozilla/plugins
$ ln -s /usr/lib/jvm/jre1.8.0/lib/i386/libnpjp2.so ~/.mozilla/plugins/


마지막으로 추가로 필요한 패키지 몇 개를 설치합니다.

$ sudo apt-get install expect gtk2-engines-pixbuf libgnome2-0 qemu-user-static libwebkitgtk-1.0-0 gettext gksu module-init-tools libudev-dev libsdl1.2debian ruby
패키지 목록을 읽는 중입니다... 완료
의존성 트리를 만드는 중입니다      
상태 정보를 읽는 중입니다... 완료
gettext 패키지는 이미 최신 버전입니다.
libgnome2-0 패키지는 이미 최신 버전입니다.
libgnome2-0 패키지 수동설치로 지정합니다.
module-init-tools 패키지는 이미 최신 버전입니다.
다음 패키지가 자동으로 설치되었지만 더 이상 필요하지 않습니다:
  libatk-wrapper-java libatk-wrapper-java-jni libgconf2-4 libgif4 libice-dev
  libpthread-stubs0-dev libsm-dev libx11-dev libx11-doc libxau-dev libxcb1-dev libxdmcp-dev
  libxt-dev x11proto-core-dev x11proto-input-dev x11proto-kb-dev xorg-sgml-doctools
  xtrans-dev
Use 'apt-get autoremove' to remove them.
다음 패키지를 더 설치할 것입니다:
  libgksu2-0 libjavascriptcoregtk-1.0-0 libudev1 libwebkitgtk-1.0-common tcl-expect udev
다음 새 패키지를 설치할 것입니다:
  expect gksu gtk2-engines-pixbuf libgksu2-0 libjavascriptcoregtk-1.0-0 libudev-dev
  libwebkitgtk-1.0-0 libwebkitgtk-1.0-common qemu-user-static tcl-expect
다음 패키지를 업그레이드할 것입니다:
  libudev1 udev
2개 업그레이드, 10개 새로 설치, 0개 제거 및 146개 업그레이드 안 함.
18.6 M바이트/19.6 M바이트 아카이브를 받아야 합니다.
이 작업 후 130 M바이트의 디스크 공간을 더 사용하게 됩니다.
계속 하시겠습니까? [Y/n]


이제 타이젠 SDK 설치를 위한 준비가 끝났습니다.

타이젠 홈페이지에 방문하여 SDK를 다운로드 받습니다.



DOWNLOAD THE SDK 버튼을 누르면 Tizen SDK를 받을 수 있는 페이지가 나옵니다.

아래 페이지에 표시된 화살표 링크를 눌러 운영체제에 맞는 SDK를 고를 수 있습니다.



여기서는 64비트 우분투용 tizen-sdk_2.3.63_ubuntu-64.bin를 다운로드 받겠습니다.

다운로드 받으시고 실행권한을 주셔야 합니다.

$ chmod +x tizen-sdk_2.3.63_ubuntu-64.bin

그리고 바로 실행해보시지요.


만약, 설치가 여전히 안된다면 openjdk 때문일겁니다.

openjdk를 사용하실 필요가 없다면 지우셔도 무방합니다.

$ sudo rm -rf /usr/lib/jvm/java-8-openjdk-amd64



Install > Advanced에 들어가서,

- Package server : SDK이미지를 서버에서 내려받거나,

- SDK Image : 이미 다운로드받은 이미지로 설치할 수 있습니다.

여기서는 Package server를 선택합니다.

그리고 Install을 누르면 다음 항목으로 넘어갑니다.



여기서는 mobile & wearable을 모두 설치할 것이므로 Custom 버튼을 누릅니다.

라디오 버튼이 체크박스로 바뀌면,

Mobile-2.3 / Wearable-2.3 / Other tools를 모두 체크합니다.

다음으로 넘어가면 라이센스 항목이 나옵니다.



라이센스를 자세히 읽어보시고...

I agree 버튼을 눌러 다음으로 넘어가세요.



기본적으로 Installation Location은 ~/tizen-sdk로 되어 있고,

Data Location은 ~/tizen-sdk-data로 되어 있습니다.

다른 경로를 원하시면 변경하실 수 있습니다.

여기서는 그대로 사용하도록 하겠습니다.

이제 Install 버튼을 누르면 설치가 됩니다.

서버로부터 패키지를 다운로드 받고 설치하기 때문에 서버 상태에 따라 시간이 많이 걸릴 수도 있습니다.


설치가 완료되면 바로 Tizen SDK를 사용하실 수 있습니다.

개발의 재미에 빠져보세요~


끝_

  1. 앨리삵 2015.10.05 20:25 신고

    우분투에서 타이젠 빌드하려고 고군분투중입니다.
    한줄기 빛과 같은 블로그 포스팅 감사해요 ㅠㅅㅠ

  2. 정말 멋진 홈피를 가지고 계십니다! :)
    그래픽스 전문가에게 직접 배운다는 생각으로 열심히 방문해야겠습니다. 하핫.



일시 : 2015년 7월 30일 ~ 31일

장소 : 인도 남부 벵갈루루 리츠칼튼 호텔

웹사이트 : http://tizendevsummit.com/index.html



인도 남부 벵갈루루의 위치는 위의 지도에서 확인하실 수 있습니다.

7월 30일 기준으로 리츠칼튼 호텔 1박에 28만원입니다.

아직 방이 여유가 있으니 예약하고 싶어지는군요.


타이젠 개발자 회담은 앱 & 플랫폼 개발자를 위한 행사입니다.

올해 인도에서 출시된 Mobile Z1은 물론이고 Wearable, TV, IoT까지 행사내용에 포함되어 있습니다.

앱개발자, 플랫폼 디자이너, ISV업체, OEM업체, 하드웨어업체, 소프트웨어업체 등 기술 컨텐츠에 관심을 가질만한 사람은 누구나 참석할 수 있습니다.

C언어 기반의 Native앱을 개발하는 개발자와 HTML5 기반의 웹앱을 개발하는 개발자 모두에게 이틀동안 타이젠에 대해 설명하는 자리가 되겠군요.


이틀간 열리는 행사에서 흥미로운 주제를 몇 개 뽑아보았습니다.

첫날 점심시간 이후에 열리는 "Breakthrough Games with Tizen"이 흥미롭습니다.



삼성전자 타이젠 R&D 그래픽스팀의 최성열 연구원께서 발표하시는군요.

OpenGl-ES와 DALi 툴킷으로 게임그래픽개발에 대해 설명하는 자리가 되겠네요.


같은 시간에 열리는 다른 세션도 구미가 당깁니다.

드디어 TV SDK가 나오나 봅니다.

삼성전자 타이젠 스마트 TV가 국무총리 대상을 받았는데요.

타이젠 TV에 올라가는 앱을 개발하는 SDK에 대한 세션입니다.

SDK 환경 및 가이드를 해주는 자리가 되겠네요.

C언어 기반의 Native 앱보다는 HTML5 기반의 웹앱을 위한 세션으로 보입니다.


다음 시간에 열리는 세션 중에 웨어러블 세션이 있습니다.

타이젠 플랫폼은 이미 삼성 기어 시리즈에 탑재되어 있습니다.

웨어러블 플랫폼의 주요 기능에 대해 전파하는 자리가 되겠네요.

이 세션 역시 웹앱 위주로 설명하는 자리가 되겠네요.


16:30분부터는 타이젠 UI Framework인 EFL과 DALi에 대해 설명하는 자리도 있습니다.

타이젠 네이티브앱에서 사용하는 EFL과 DALi를 엿볼 수 있습니다.

단지 UI 컴포넌트를 설명하는 자리는 아니고,

scene graph나 opengl 가속렌더링, mainloop, thread 등에 대해 훑어보는군요.

게다가 여기에 DALi라는 3D UI 엔진도 소개할 예정입니다.

1시간이 무척이나 짧아보이네요.


이제 둘째날로 넘어갑니다.

둘째날 아침 10시에 타이젠 플랫폼의 퍼포먼스에 대한 세션이 있습니다.

Z1 스마트폰의 사양을 생각할 때,

부팅속도가 상당히 최적화되어 있다고 생각하는데요.

모바일이나 웨어러블 앱을 최적화하는 툴&팁을 공유하는 자리가 되겠군요.


그 다음 시간에는 IoTivity에 대한 세션도 있습니다.

IoTivity의 API셋을 소개하는 자리가 될 것으로 보이는데요.

Things & 센서 관리 서비스를 엿볼 수 있겠군요.

Protocol Manager Service를 사용하여 OIC 규약에 맞게 다양한 디바이스들을 연결할 수 있다고 하니 실체가 궁금해집니다.

IoTivity는 삼성전자가 리드하고 있는 만큼,

타이젠 플랫폼과도 거리를 좁히고 있나보군요.


둘째날 점심에 열리는 세션에서 타이젠의 방향을 볼 수 있습니다.

타이젠앱은 모바일, 웨어러블, TV에서 동작합니다.

다양한 프로파일, 특히 TV까지 포괄하는 플랫폼은 타이젠이 처음이겠군요.

마무리하는 세션이니만큼 타 플랫폼과 차별화되는 특장점들이 나와주겠군요.


이틀간 열리는 짧은 행사입니다.

플랫폼을 설명하기에 이틀은 터무니없이 짧죠.

그렇지만, 타이젠을 미래의 먹거리로 생각하는 사람에게는 좋은 시작이 되겠네요.


Early bird로 1,999루피(35,000원)에 입장권을 구매할 수 있습니다.

이 정도면 거의 공짜...

이참에 휴가내어 인도로 놀러가야겠군요.


끝_

  1. 김재천 2015.06.27 19:15

    잘봤습니다^^

타이젠 네이티브 앱을 개발하려면 네이티브 API를 사용해야합니다.

그리고 일부 네이티브 API는 별도의 Privilege 권한이 있어야 사용할 수 있습니다.


타이젠 네이티브 API에 어떤 Privilege가 있는지 살펴보고,

어떤 식으로 관리되고 있는지 훔쳐보도록 하겠습니다.


Tizen 2.3으로 오픈된 소스를 기반으로 정리하였습니다.



안녕하세요, Tizen 개발자 윤진입니다.


타이젠 SDK에서 관리하는 Privilege를 찾아보았습니다.

git://review.tizen.org/sdk/doc/content의 master브랜치,

api-reference/native/tools/privilege_desc.txt 파일에 Privilege가 명시되어 있습니다.

privilege_desc.txt를 그대로 가져오고,

각각 한글로 간단하게 번역해두었습니다.


http://tizen.org/privilege/account.read
Allows the application to read accounts.
계정을 읽을 수 있습니다.

http://tizen.org/privilege/account.write   
Allows the application to create, update, and delete accounts.   
계정을 생성, 수정, 삭제할 수 있습니다.

http://tizen.org/privilege/alarm   
Allows the application to set an alarm and wake the device up at the scheduled time.   
알람을 설정하고 디바이스를 알람시간에 깨울 수 있습니다.

http://tizen.org/privilege/application.launch   
Allows the application to be opened conditionally or to open another application.   
앱을 상황에 맞게 열거나 다른 앱을 런칭할 수 있습니다.

http://tizen.org/privilege/appmanager.kill   
Allows the application to close another application.
다른 앱을 닫을 수 있습니다.

http://tizen.org/privilege/appmanager.launch   
Allows the application to manage another application opening conditionally.   
다른 앱을 조건에 맞게 열 수 있습니다.

http://tizen.org/privilege/appmanager.setting   
Allows the application to read and update default application preferences.   
디폴트 앱의 설정을 읽고 수정할 수 있습니다.

http://tizen.org/privilege/appsetting   
Allows the application to manage application settings.
앱의 설정을 관리할 수 있습니다.    

http://tizen.org/privilege/appusage   
Allows the application to receive notifications about foreground application changes.   
foreground에 있는 앱의 변화를 받을 수 있습니다.

http://tizen.org/privilege/appwidgetprovider.install   
Allows the application to add Dynamic Box to the home screen.   
다이나믹박스를 홈스크린에 추가할 수 있습니다.

http://tizen.org/privilege/audiomanager.route   
Allows the application to set audio input and output routes and receive notifications about route events.   
오디오 입출력 경로를 설정하고 경로이벤트에 대한 알림을 받을 수 있습니다.

http://tizen.org/privilege/audiorecorder   
Allows the application to manage audio recording.   
오디오레코딩을 관리할 수 있습니다.

http://tizen.org/privilege/bluetooth.admin   
Allows the application to change Bluetooth settings, such as turning Bluetooth on or off, and setting the local device name.   
블루투스 설정을 변경할 수 있습니다. - 블루투스 on / off, 로컬 디바이스 이름 변경

http://tizen.org/privilege/bluetooth.gap   
Allows the application to use the Generic Access Profile (GAP), for example, to scan for and pair devices.   
Generic Access Profile(GAP)을 사용할 수 있습니다. - 페어 디바이스를 스캔하기 등

http://tizen.org/privilege/bluetooth.health   
Allows the application to use the Health Device Profile (HDP), for example, to send health data.   
Health Device Profile을 사용할 수 있습니다. - 헬스 데이타 보내기 등

http://tizen.org/privilege/bluetooth.opp   
Allows the application to use the Object Push Profile (OPP), for example, to send files.
Object Push Profile을 사용할 수 있습니다. - 파일 보내기 등

http://tizen.org/privilege/bluetooth.spp   
Allows the application to use the Serial Port Profile (SPP), for example, to send serial data.   
Serial Port Profile을 사용할 수 있습니다. - 시리얼 데이타 보내기 등

http://tizen.org/privilege/bluetoothmanager   
Allows the application to change Bluetooth system settings related to privacy and security, such as the visible mode.   
블루투스 시스템 설정(프라이버시, 시큐리티)을 변경할 수 있습니다. - visible 모드 등

http://tizen.org/privilege/bookmark.read   
Allows the application to read bookmarks.   
북마크를 읽을 수 있습니다.

http://tizen.org/privilege/bookmark.write   
Allows the application to create, update, and delete bookmarks.   
북마크를 생성, 수정, 삭제할 수 있습니다.

http://tizen.org/privilege/calendar.read   
Allows the application to read schedule and task information.   
스케쥴과 업무 정보를 읽을 수 있습니다.

http://tizen.org/privilege/calendar.write
Allows the application to create, update, and delete schedule and task information.   
스캐쥴과 업무 정보를 생성, 수정, 삭제할 수 있습니다.

http://tizen.org/privilege/callforward
Allows the application to control the call forwarding service.   
콜 포워딩 서비스를 컨트롤할 수 있습니다.

http://tizen.org/privilege/camera
Allows the application to manage device cameras to preview and capture pictures.
프리뷰를 보고 사진을 찍을 수 있습니다.

http://tizen.org/privilege/cellbroadcast
Allows the application to manage the Cell Broadcast Service (CBS).   
Cell Broadcast Service를 관리할 수 있습니다.

http://tizen.org/privilege/certificate.read
Allows the application to read certificates.   
인증서를 읽을 수 있습니다.

http://tizen.org/privilege/certificate.write
Allows the application to add, update, and delete certificates.   
인증서를 추가, 수정, 삭제할 수 있습니다.

http://tizen.org/privilege/contact.read
Allows the application to read contacts.
연락처 정보를 읽을 수 있습니다.

http://tizen.org/privilege/contact.write
Allows the application to add, update, and delete contacts.   
연락처 정보를 추가, 수정, 삭제할 수 있습니다.

http://tizen.org/privilege/content.read
Allows the application to read content.   
컨텐츠를 읽을 수 있습니다.

http://tizen.org/privilege/content.write
Allows the application to create, update, and delete content.   
컨텐츠를 추가, 수정, 삭제할 수 있습니다.

http://tizen.org/privilege/customnetaccount
Allows the application to use the Internet with a specified bearer.
특정 배러와 함께 인터넷을 사용할 수 있습니다.

http://tizen.org/privilege/datacontrol.consumer
Allows the application to access specific data exported by other applications.
다른 앱이 준 데이타에 접근할 수 있습니다.

http://tizen.org/privilege/dns
Allows the application to provide the Domain Name Service (DNS).   
Domain Name Service를 제공할 수 있습니다.

http://tizen.org/privilege/download
Allows the application to manage HTTP downloads.   
HTTP 다운로드를 관리할 수 있습니다.

http://tizen.org/privilege/geolocationpermission.read
Allows the application to read geolocation permissions.   
지리정보 권한을 읽을 수 있습니다.

http://tizen.org/privilege/geolocationpermission.write
Allows the application to delete geolocation permissions.   
지리정보 권한을 지울 수 있습니다.

http://tizen.org/privilege/http
Allows the application to communicate with the HTTP server.   
HTTP 서버와 통신할 수 있습니다.

http://tizen.org/privilege/ime
Allows the application to provide a way to input characters and symbols into an associated input field.   
소프트웨어 키보드를 제공할 수 있습니다.

http://tizen.org/privilege/imemanager
Allows the application to manage the installed input methods.    
설치된 소프트웨어 키보드를 관리할 수 있습니다.


http://tizen.org/privilege/inputmanager
Allows the application to generate touch and key events, and capture screens.    
터치, 키 이벤트를 만들고 스크린을 캡쳐할 수 있습니다.


http://tizen.org/privilege/location
Allows the application to use the user location data.  
유저의 지리데이터를 이용할 수 있습니다.  

http://tizen.org/privilege/lockmanager
Allows the application to unlock the device.   
디바이스를 언락할 수 있습니다.

http://tizen.org/privilege/messaging.read
Allows the application to receive messages, and to retrieve messages from the message boxes.   
메시지를 받고 메시지박스에서 메시지를 긁어올 수 있습니다.

http://tizen.org/privilege/messaging.write
Allows the application to write SMS, MMS, and email messages.   
SMS, MMS와 이메일을 쓸 수 있습니다.

http://tizen.org/privilege/netstatisticsmanager
Allows the application to reset network statistics.   
네트워크 정보를 리셋할 수 있습니다.

http://tizen.org/privilege/network.account
Allows the application to manage network accounts.   
네트워크 계정을 관리할 수 있습니다.

http://tizen.org/privilege/network.connection
Allows the application to manage network connections.   
네트워크 커넥션을 관리할 수 있습니다.

http://tizen.org/privilege/network.statistics
Allows the application to read network statistics.   
네트워크 정보를 읽을 수 있습니다.

http://tizen.org/privilege/networkmanager
Allows the application to update system network accounts.
시스템 네트워크 계정을 수정할 수 있습니다.

http://tizen.org/privilege/nfc.admin
Allows the application to change NFC settings, such as turning NFC on or off.   
NFC 설정을 변경할 수 있습니다. - NFC on / off

http://tizen.org/privilege/nfc.common
Allows the application to use NFC common features.   
NFC 공통 기능을 사용할 수 있습니다.

http://tizen.org/privilege/nfc.p2p
Allows the application to push NFC messages to other devices.   
다른 기기에 NFC 메시지를 푸쉬할 수 있습니다.

http://tizen.org/privilege/nfc.tag
Allows the application to read and write NFC tag information.   
NFC 태그정보를 읽고 쓸 수 있습니다.

http://tizen.org/privilege/nfcmanager
Allows the application to change NFC system settings, such as turning on or off reserved push, which sends predefined NDEF messages.   
NFC 시스템 설정을 변경할 수 있습니다. - 예약 푸쉬를 on / off 하기

http://tizen.org/privilege/notification
Allows the application to provide user notifications, such as messages and badges.   
유저 알림을 제공할 수 있습니다. - 메시지나 배지

http://tizen.org/privilege/package.info
Allows the application to receive package information.   
패키지 정보를 받을 수 있습니다.

http://tizen.org/privilege/packagemanager.info
Allows the application to receive detailed package information.   
자세한 패키지 정보를 받을 수 있습니다.

http://tizen.org/privilege/packagemanager.install
Allows the application to install or uninstall application packages.   
앱 패키지를 설치 혹은 삭제할 수 있습니다.

http://tizen.org/privilege/packagemanager.setting
Allows the application to set package configurations.   
패키지 설정값을 변경할 수 있습니다.

http://tizen.org/privilege/power
Allows the application to hold and control power states.   
파워 상태를 유지하거나 조절할 수 있습니다.

http://tizen.org/privilege/privacymanager.read
Allows the application to read privacy settings.   
프라이버시 설정값을 읽을 수 있습니다.

http://tizen.org/privilege/privacymanager.write
Allows the application to change privacy settings.   
프라이버시 설정을 변경할 수 있습니다.  

http://tizen.org/privilege/privilegemanager.read
Allows the application to read privilege information.   
프라이버스 정보를 읽을 수 있습니다.

http://tizen.org/privilege/push
Allows the application to receive push notifications.   
푸쉬 알림을 받을 수 있습니다.

http://tizen.org/privilege/secureelement
Allows the application to access to secure smart card chip such as UICC/SIM, embedded secure element, or secure SD card.   
UICC/SIM 카드에 접근할 수 있습니다.

http://tizen.org/privilege/setting
Allows the application to update or read user settings.   
유저 설정을 읽거나 수정할 수 있습니다.

http://tizen.org/privilege/settingmanager.read
Allows the application to read security or privacy settings.   
시큐리티나 프라이버시 설정을 읽을 수 있습니다.

http://tizen.org/privilege/settingmanager.write
Allows the application to change security or privacy settings.   
시큐리티나 프라이버시 설정을 변경할 수 있습니다.

http://tizen.org/privilege/shortcut.install
Allows the application to add and remove shortcuts from the home screen.   
홈스크린에서 숏컷을 추가 혹은 삭제할 수 있습니다.

http://tizen.org/privilege/smstrigger
Allows the application to receive SMS messages from a specified port.
특정 포트로부터 SMS 메시지를 받을 수 있습니다.

http://tizen.org/privilege/socket
Allows the application to communicate with other devices and servers.
다른 디바이스나 서버와 통신할 수 있습니다.

http://tizen.org/privilege/system
Allows the application to access system information.
시스템 정보에 접근할 수 있습니다.

http://tizen.org/privilege/systemmanager
Allows the application to access secure system information.
보안 시스템 정보에 접근할 수 있습니다.

http://tizen.org/privilege/telephony
Allows the application to retrieve telephony information, such as network, SIM, and call details.
텔레포니 정보를 긁어올 수 있습니다. - Network, SIM, Call 상제정보

http://tizen.org/privilege/telephonymanager
Allows the application to configure the mobile networks.
모바일 네트워크를 설정할 수 있습니다.


http://tizen.org/privilege/uimanager
Allows the application to manage UI properties.   
UI 속성을 관리할 수 있습니다.

http://tizen.org/privilege/userprofile.read
Allows the application to read the user profile.
유저 프로파일을 읽을 수 있습니다.

http://tizen.org/privilege/userprofile.write
Allows the application to manage the user profile.
유저 프로파일을 관리할 수 있습니다.

http://tizen.org/privilege/vibrator
Allows the application to turn on and use vibrate.
진동키능을 켜겨나 사용할 수 있습니다.

http://tizen.org/privilege/videorecorder
Allows the application to manage video recording with the camera.
카메라를 사용하여 비디오 녹화를 할 수 있습니다.

http://tizen.org/privilege/wappush
Allows the application to manage WAP push messages.
WAP 푸쉬 메시지를 관리할 수 있습니다.

http://tizen.org/privilege/web.privacy
Allows the application to manage the user data stored by the Web control or browser.
웹컨트롤이나 브라우저에서 저장된 유저데이터를 관리할 수 있습니다.

http://tizen.org/privilege/web.service
Allows the application to display Web content or use the Web content-related API.
웹컨텐트를 표시하고 웹컨텐트 관련 함수를 사용할 수 있습니다.

http://tizen.org/privilege/wifi.admin
Allows the application to manage Wi-Fi on the device, such as turning Wi-Fi on or off, and connecting to AP.
Wi-Fi를 관리할 수 있습니다. - 와이파이를 on / off 하고, AP에 접속하기

http://tizen.org/privilege/wifi.read
Allows the application to request Wi-Fi information.
Wi-Fi 정보를 요청할 수 있습니다.

http://tizen.org/privilege/wifi.wifidirect.admin
Allows the application to manage Wi-Fi Direct on the device.
Wi-Fi Direct 기능을 관리할 수 있습니다.

http://tizen.org/privilege/wifi.wifidirect.read
Allows the application to request Wi-Fi Direct information.
Wi-Fi Direct 정보를 요청할 수 있습니다.

http://tizen.org/privilege/wifimanager
Allows the application to manage the Wi-Fi system settings, such as updating AP information.
Wi-Fi 시스템 설정을 관리할 수 있습니다. - AP 정보 갱신 등


위에서 기술된 Privilege를 앱에서 사용하는 것이지요.

플랫폼 버전마다 사용할 수 있는 Privilege는 다를 수 있습니다.


SDK에서 개발할 때는,

- Project Explorer에서 자신의 프로젝트 내에 있는 tizen-manifest.xml 선택

- Tizen Manifest Editor에서 Privileges 탭 선택

- Add 버튼을 눌러 Privilege를 추가할 수 있습니다.



SDK 외부에서 개발할 경우에는,

각 앱의 xml파일에 직접 Privilege를 기술해야 합니다.


<?xml version="1.0" encoding="utf-8" ?>
<manifest xmlns="http://tizen.org/ns/packages" package="org.tizen.data-provider-slave" version="1.0.0" install-location="auto" support-mode="screen-reader">
    <!-- ... 생략 ... -->
    <ui-application appid="org.tizen.data-provider-slave" exec="/usr/apps/org.tizen.data-provider-slave/bin/data-provider-slave" nodisplay="true" multiple="true" type="capp" taskmanage="false">
    <!-- ... 생략 ... -->
    </ui-application>
    <privileges>
        <privilege>http://tizen.org/privilege/location</privilege>
        <privilege>http://developer.samsung.com/tizen/privilege/privacymanager.read</privilege> <!-- weather -->
    </privileges>
    <!-- ... 생략 ... -->
</manifest>


위의 코드는 git://review.tizen.org/apps/livebox/data-provider-slave의 tizen_2.3 브랜치,

org.tizen.data-provider-slave.xml 파일에서 따왔습니다.

SDK 외부에서 개발하는 앱은 위처럼 자신에게 필요한 Privilege를 xml 파일에 직접 추가해야 하지요.


Security/Tizen 2.X smack-privilege-config


앱에 추가된 Privilege는,

앱이 설치되는 시점에 Privilege 레벨에서 Smack 레벨로 변경됩니다.


git://review.tizen.org/framework/security/smack-privilege-config의 tizen_2.3 브랜치,

permissions_mobile/permissions_2_3_0 디렉토리에는 수많은 *.smack 파일이 있습니다.

그 중 EFL_org.tizen.privilege.camera.smack 파일을 열어보겠습니다.


~APP~ device::camera rw----
~APP~ mm_qcamera_daemon -w----
~APP~ privilege::tizen::camera rw----
~APP~ system::vconf_multimedia rw----


카메라를 사용하기 위해 필요한 스맥룰이 명시되어 있습니다.

~APP~은 위의 privilege를 설정한 앱의 스맥레이블로 치환될 것입니다.

간단히 내용을 살펴보면,

device::camera에 접근하기 위한 읽고 쓰는 권한,

mm_qcamera_daemon에 쓰기 권한,

privilege::tizen::camera를 읽고 쓰는 권한,

system::vconf_multimedia를 읽고 쓰는 권한 등의 스맥권한을 부여합니다.


이렇게 부여된 스맥레이블은,

네이티브 API가 사용하는 파일/디렉토리 등에 접근할 수 있게 할겁니다.

만약 제대로된 스맥레이블 없이 파일/디렉토리에 접근하려하면 Permission denied 에러가 나오겠죠.


git://review.tizen.org/framework/security/privilege-checker의 tizen_2.3 브랜치,

capi/src/privilege_checker.c 소스를 살펴보면,

앱이 Privilege를 가지고 있는지 여부를 직접 체크하는 부분도 있습니다.


int privilege_checker_check_package_privilege(const char* package_id, const char* privilege_name)
{
    // ... 생략 ...
    //find privilege in cache
    for(i=0; i<g_cached_privileges_size; i++)
    {
        if(hash_value == g_cached_privileges[i].hash_value)
        {
            matched_index = i;
            ++matched_cnt;
        }
    }

    if(matched_cnt == 1){
        LOGD("Found cached hash privilege");
        return PRIV_CHECKER_ERR_NONE;

    }
    // ... 생략 ...
    LOGD("Not Found cached privilege");

    // ... 생략 ...
    res = pkgmgrinfo_pkginfo_foreach_privilege(handle, __privilege_checker_privilege_list_by_pkgid_cb, user_data);
    // ... 생략 ...

    TryReturn(g_isMached == true, PRIV_CHECKER_ERR_INVALID_PRIVILEGE, "[PRIV_CHECKER_ERR_INVALID_PRIVILEGE] %s privilege denied", privilege_name);

    return PRIV_CHECKER_ERR_NONE;
}

그래서 API를 수행할 때, API를 호출한 앱의 권한을 체크하여 수행여부를 결정하게 됩니다.

예를 들어,

git://review.tizen.org/framework/api/application의 tizen_2.3 브랜치,

app_control/app_control.c 소스를 보면 Call Operaion로 런칭요청이 온 경우 Privilege가 있는지 체크합니다.


int app_control_send_launch_request(app_control_h app_control, app_control_reply_cb callback, void *user_data)
{
    // ... 생략 ...
    // Check the privilege for call operation
    if (!strcmp(operation, APP_CONTROL_OPERATION_CALL))
    {
    // ... 생략 ...

        ret = privilege_checker_check_package_privilege(pkg_id, "http://tizen.org/privilege/call");
        pkgmgrinfo_appinfo_destroy_appinfo(app_info);
        if (ret != PRIV_CHECKER_ERR_NONE)
        {
            if (ret == PRIV_CHECKER_ERR_INVALID_PRIVILEGE)
            {
                return app_control_error(APP_CONTROL_ERROR_PERMISSION_DENIED, __FUNCTION__, "no privilege for Call operation");
            }
        }
    }
}


Privilege는 Smack으로 치환되어 관리될 수 있고,

Privilege 자체로 관리될 수도 있습니다.


끝_


* SMACK에 대한 이야기를 쌓아본다

http://storycompiler.tistory.com/51


* References

https://wiki.tizen.org/wiki/Security/Tizen_2.X_smack-privilege-config

https://wiki.tizen.org/wiki/Security/Tizen_2.X_cert-svc#Additional_certificate_service_APIs

웨어러블 디바이스가 하나둘씩 출시되고 있습니다.

삼성전자가 출시한 시계만 해도,

기어 1, 기어 Fit, 기어 2, 기어 S가 있습니다.

거기에 원형 디스플레이의 기어가 조만간 출시될 예정이죠.


이 원형 디스플레이를 가진 기어를 위한 SDK가 드디어 배포되었습니다.

Early Access Program으로 사전등록한 개발자에게 SDK를 배포하고 있네요.

사전등록을 위해서 까다로운 절차는 없습니다.

그저 Join만 하면 됩니다. :)



안녕하세요, Tizen 개발자 윤진입니다.


Early Access Program에 참여하라는 메일입니다.

"Get ready for the next gear"

문구 뒤로 보이는 원형 디스플레이가 눈에 띕니다.

원형 디스플레이에는 지역정보와 시간정보 그리고 날씨정보가 노출되어 있습니다.

기어가 보여줄 정보가 되겠군요.


메일의 하단부에 "Join us" 버튼이 있네요.

버튼을 누르니 아래 사이트에 접근하더군요.

http://developer.samsung.com/wearable/sub.html



위의 사이트에는 EAP에 참여하기 위해서는 삼성 개발자 계정이 있어야 한다고 언급되어 있습니다.

삼성계정이 없으면 이 기회에 하나 만들고,

Sign up 버튼을 눌러 SDK 사용자에 대한 간단한 정보를 입력하시죠~


승인메일이 바로 오진 않습니다.

적어도 주말에는 승인메일이 오지 않았습니다.

주말이 지난 월요일 오전 10시경에 승인메일을 받은 기억이 납니다.

시스템이 자동으로 승인을 하진 않을테고,

수동으로 신청자를 가려 승인해주는게 아닌가 합니다.


승인메일은 아래 내용을 담고 전송됩니다.

Gear SDK Early Access Program에 대한 간단한 정보가 있고,

다운로드 페이지로 접근할 수 있는 버튼이 있네요.



권한이 없는 상태에서 다운로드 페이지로는 갈 수 없습니다.

다운로드를 받으시려면 필히 승인까지 받으셔야 합니다.


다운로드 페이지에 접근하면 아래와 같은 화면이 나옵니다.

처음으로 언급된 것이 기어앱의 동작방식이네요.

기어앱은,

- Standalone : 기어만 있어도 앱을 100% 활용할 수 있는 경우

- Companion : 모바일과 기어가 연결되어야 앱을 100% 활용할 수 있는 경우

위의 두 방식 중 하나로 동작하게 됩니다.



설치 전에 설치 방법이 언급된 More Details 버튼을 누릅니다.

위의 버튼을 누르면 아래처럼 자세한 설치방법이 나옵니다.



이 설치방법은 다운로드를 마치고 설치할 때 참고하시면 됩니다.

More Details 아래에는 플랫폼별 인스톨 매니져가 있네요.

자신의 OS에 해당하는 인스톨 매니져를 다운로드 받습니다.

- MacOS 64 bits

- Ubuntu 32 bits

- Ubuntu 64 bits

- Windows 32 bits

- Windows 64 bits


다운로드를 요청하면,

라이센스가 나옵니다.



라이센스에 동의한다는 의미로 두 군데에 모두 체크를 합니다.

두 군데 모두 체크가 되면, Download라는 버튼이 생성됩니다.

Download를 눌러 타이젠 SDK를 다운로드 받습니다.


윈도우 64비트용 인스톨 메니져는 약 810메가 정도의 용량입니다.

전송속도는 시스템마다 차이가 있겠지만,

2분 남짓이면 다운로드받을 수 있습니다.


다운로드 받은 파일은 압축되어 있습니다.

압축을 풀고 실행파일을 실행합니다.

윈도우의 경우에는 "tizen-sdk_2.3.74_windows-64.exe" 실행합니다.



인스톨 매니져가 정상적으로 실행되었다면,

위의 화면을 볼 수 있습니다.


Install 혹은 Update > Advanced

위의 경로에서 Advanced 버튼을 누릅니다.



Advanced Configuration에서,

SDK image를 선택합니다.

그리고 다운로드 받은 파일 중 "tizen-sdk-image-TizenSDK_2.3.1_RC7_EAP-windows-64.zip" 파일을 선택한 후 OK를 누릅니다.


여기까지 진행되었으면 아래와 같은 화면을 보실 수 있습니다.

Profiles에는 Wearable 2.3.1만 설치할 수 있습니다.



Wearable 2.3.1이 선택된 것을 확인하고,

Install 버튼을 누릅니다.



타이젠 SDK에 대한 라이센스가 나옵니다.

I agree 버튼을 눌러주세요.


"Ready to install?"

인스톨 준비가 다 되었는지 묻습니다.



Installation Location과 Data Location의 위치를 변경할 수 있습니다.

다만 여기서는 기존에 설정된 디렉토리를 그대로 사용하도록 하겠습니다.

중간에 있는 Install 버튼을 눌러 인스톨을 시작합니다.



인스톨이 완료되면 위의 화면이 나옵니다.

Close를 누릅니다.


그리고 타이젠 IDE를 실행하여 이클립스를 띄웁니다.

왼편 하단에 Connection Explorer가 있습니다.

가장 왼편에 에뮬레이터를 닮은 버튼을 눌러 Emulator manager를 누릅니다.



아래처럼 에뮬레이터 매니져가 런칭되었습니다.

에뮬레이터 매니져는 다음 세 개의 탭으로 되어 있습니다.

- wearable-2.3.1

- custom

- all



wearable-2.3.1 탭에 진입하여,

Create New VM 아래에 있는 '+' 버튼을 누릅니다.

그러면 그 자리에 폰 모양의 에뮬이 하나 생깁니다.



폰 오른쪽에 Detail을 설정할 수 있는 부분이 있습니다.

거기서 Name 정도를 바꿔줄 수 있습니다.

그리고 아래 Confirm을 누릅니다.



위의 이미지처럼 에뮬레이터가 하나 등록되었습니다.

사이즈는 360x360입니다.

원형 디스플레이가 360x360이겠지요.


에뮬레이터의 재생버튼을 눌러,

에뮬레이터를 런칭합니다.



원형 디스플레이를 가진 wearable 2.3.1 버전을 보실 수 있습니다.

이번에 추가된 장치로 오른 편에 용두가 있습니다.

그리고 원형디스플레이를 감싸고 있는 rotary도 있습니다.

신규 피쳐를 이용하여,

재미난 앱을 짤 수 있겠군요.


끝_




타이젠에 올라가는 앱은 타이젠 SDK를 사용하여 빌드할 수 있습니다.

하지만, 타이젠 플랫폼 자체를 빌드하기 위해서는 SDK로는 안됩니다.


타이젠 그룹에서는 타이젠 플랫폼의 패키지들을 빌드할 수 있는 환경을 제공하고 있습니다.

타이젠 개발자들은 실제로 이러한 방법으로 패키지를 만들어내고 있습니다.


지금 이 순간에도,

세계 각지의 타이젠 개발자들은 타이젠 빌드서버에 접속하여 빌드를 하고 있습니다.



안녕하세요, Tizen 개발자 윤진입니다.


gbs를 사용하려면 우선 설치가 되어 있어야겠지요?

gbs는 아래 포스팅을 참고하여 설치하실 수 있습니다.

"우분투에 Tizen 플랫폼 툴인 gbs & sdb 설치해보기, http://storycompiler.tistory.com/35"


우분투 15.04 환경에서 gbs가 동작하지 않는 문제가 있습니다.

이 문제는 아래 포스팅을 참고해주세요.

[Tizen] 우분투 15.04에서 gbs 실행에러 처리


gbs는 Git Build System의 약자로,

타이젠 플랫폼의 패키지를 빌드하기 위한 커맨드라인 툴입니다.

- Git repo.를 기준으로 tar.gz 파일을 생성한 뒤,

- 로컬 빌드를 수행하고,

- 타이젠 메인에 업로드할 수 있습니다.



gbs로 빌드하기 위해서는 위의 그림 좌우에 있는 4개의 박스가 필요합니다.

- Source Code

   git을 기반으로한 소스 repository는 빌드 대상이 되겠지요.

   타이젠 오픈소스는 아래 포스팅을 참고하여 클론 받으실 수 있습니다.

   "Tizen 오픈소스 모두 내려받기, http://storycompiler.tistory.com/16"

- Build config

   gbs의 설정파일은 빌드하기 위한 방법이 명시되어 있습니다.

- Repositories

   빌드하기 위한 재료입니다. 플랫폼을 이루는 rpms가 저장된 곳이지요.

- Local repository

   플랫폼을 이루는 패키지 중 일부만 임의로 변경하고자 하는 경우에,

   Local repo.에 패키지를 설치해두면 됩니다.

   gbs는 빌드를 하며 Local repo.를 먼저 살펴보고 패키지를 찾기 때문이죠.

   Local repo.에 패키지가 없으면 Repositories에서 찾습니다.

   대부분 패키지는 Repositories에서 찾게 되겠지요.


gbs, source를 구비했다면 이제 빌드를 위한 기본설정을 할 차례입니다.

설정을 한 파일은 아래 위치 중 한 군데에 놓으면 됩니다.


  • $PWD/.gbs.conf: project-specific configuration settings that affect only the specific project in the specified working directory. These settings have the highest precedence.
  • /home/<user>/.gbs.conf: user-specific configuration settings that affect only the specified user.
  • /etc/gbs.conf: system-wide configuration settings that affect the entire system. These settings have the lowest precedence.


상단에 있는 $PWD/.gbs.conf의 우선순위가 가장 높고,

/etc/gbs.conf의 우선순위가 가장 낮습니다.

우선순위 순으로 conf 파일을 찾아보고 높은 순위의 conf 설정대로 빌드가 수행되죠.


gbs 명령은 빌드하고자 하는 프로젝트의 디렉토리에서 수행합니다.

혹은 다수의 프로젝트를 빌드하기 위해서는 프로젝트들이 모여있는 디렉토리에서 하면 되지요.

$PWD는 gbs를 수행하는 그 위치를 의미합니다.

gbs가 실행되면 우선적으로 명령을 실행한 디렉토리에 .gbs.conf 파일이 있는지 확인하게 됩니다.


위의 위치에 .gbs.conf가 없다면,

일반적으로 설정 파일을 놓는,

/home/<user>/.gbs.conf 파일을 참조하게 됩니다.

다수의 프로젝트를 빌드하려면 위의 위치에 .gbs.conf 파일을 설치하는 편이 좋습니다.


마지막으로 /etc/gbs.conf는 어느 사용자나 사용할 수 있는 설정파일입니다.

다수의 사용자를 위해 gbs 환경을 제공해야하는 경우에 사용하면 됩니다.


그 외의 위치에 다른 이름으로 conf 파일을 만들고자 하는 경우도 있을 겁니다.

그럴 때에는 gbs 명령을 수행할 때 별도의 위치에 다른 이름으로 있다는 것을 gbs에 알려줘야 합니다.

-c 옵션으로 conf 파일의 위치를 넘겨줄 수 있습니다.

$ gbs -c ~/my-gbs.conf build ...


gbs를 설치하였다면,

기본적으로 /home/<user>/.gbs.conf 파일이 설치되어 있을 겁니다.

그 내용을 먼저 살펴보겠습니다.


$ cat ~/.gbs.conf 
[general]
#Current profile name which should match a profile section name
profile = profile.tizen

[profile.tizen]
#Common authentication info for whole profile
#user =
#CAUTION: please use the key name "passwd" to reset plaintext password
#passwd =
obs = obs.tizen
#Comma separated list of repositories
repos = repo.tizen_latest
#repos = repo.tizen_main, repo.tizen_base
#Build config for gbs build
#buildconf = <patch/to/build-config-file>
#Comma separated list of additional packages be excluded building
#exclude_packages = libtool,gettext

[obs.tizen]
#OBS API URL pointing to a remote OBS.
url = https://api.tizen.org
#Optional user and password, set if differ from profile's user and password
#user =
#passwd =

#Repo section example
[repo.tizen_latest]
#Build against repo's URL
url = http://download.tizen.org/releases/daily/trunk/ivi/latest/
#Optional user and password, set if differ from profile's user and password
#user =
#passwd =

#Individual repo is also supported
#[repo.tizen_base]
#url = http://download.tizen.org/releases/daily/trunk/ivi/latest/repos/base/ia32/packages/
#[repo.tizen_main]
#url = http://download.tizen.org/releases/daily/trunk/ivi/latest/repos/ivi/ia32/packages/


위의 설정값 중 굵은 글씨로 표시된,

[repo.tizen_latest] 섹션의 url 필드값만 변경하면 빌드를 할 수 있습니다.

url 필드에 타이젠 플랫폼의 릴리스 바이너리 주소를 입력하면 드디어 빌드를 위한 재료가 준비된 셈입니다.

url 필드에 입력하는 주소로 빌드에 사용할 플랫폼의 버전을 선택할 수 있습니다.

타이젠 플랫폼에는 2015. 6. 6 현재 10개의 릴리스 버전이 있습니다.

각각의 릴리스 버전 아래에 url을 명시해두었으니 복사해서 쓰시면 됩니다.


- Tizen 1.0 : 2012년 4월 30일 첫번째 빌드

   url = 없음, gbs 빌드 대상이 아니었음.

- Tizen 2.0 Emulator : 2013년 2월 18일 7번째 빌드

   url = http://download.tizen.org/releases/2.0-emul/tizen-2.0-emul_20130218.7/repos/base/ia32/packages/

- Tizen 2.0 : 2013년 2월 19일 4번째 빌드

   url = http://download.tizen.org/releases/2.0/latest/

- Tizen 2.1 Beta : 2013년 5월 5일 3번째 빌드

   url = http://download.tizen.org/releases/2.1b/tizen-2.1_20130505.3/

- Tizen 2.1 : 2013년 5월 17일 6번째 빌드

   url = http://download.tizen.org/releases/2.1/latest/

- Tizen 2.2 Beta : 2013년 7월 1일 5번째 빌드

   url = 없음

- Tizen 2.2 Emulator : 2013년 7월 19일 2번째 빌드

   url = http://download.tizen.org/releases/2.2-emul/latest/

- Tizen 2.2 : 2013년 7월 19일 3번째 빌드

   url = http://download.tizen.org/releases/2.2/latest/

- Tizen 2.2.1 : 2013년 11월 7일 3번째 빌드

   url = http://download.tizen.org/releases/2.2.1/latest/

- Tizen 2.3 : 2015년 3월 11일 3번째 빌드

   [mobile]

   url = http://download.tizen.org/releases/2.3/2.3-mobile/tizen-2.3-mobile_20150311.3/

   [wearable]

   url = http://download.tizen.org/releases/2.3/2.3-wearable/tizen-2.3-wearable_20150311.3/


각각의 릴리스 버전은 아래 사이트에서도 확인할 수 있습니다.

릴리스 사이트 : http://download.tizen.org/releases/



위에서 클론을 받은 타이젠 소스 repo.에서 gbs를 수행해보시죠.

$ gbs build -A i586
info: generate repositories ...
info: build conf has been downloaded at:
      /var/tmp/usr-gbs/tizen.conf
info: start building packages from: /home/user/tizen/apps/apps.home.settings (git)
2015-06-07 00:38 +0900
gbs 0.23.2
info: prepare sources...
info: start export source from: /home/user/tizen/apps/apps.home.settings ...
Argument "" isn't numeric in numeric eq (==) at /usr/bin/depanneur line 642.
warning: Deprecated option '--git-export-only', please use '--no-build' instead!
info: Creating (native) source archive org.tizen.setting-0.1.98.tar.gz from 'HEAD'
info: package files have been exported to:
     /home/user/GBS-ROOT/local/sources/tizen/org.tizen.setting-0.1.98-99
info: retrieving repo metadata...
info: parsing package data...
info: building repo metadata ...
info: resolving skipped packages ...
info: package dependency resolving ...
info: next pass:
org.tizen.setting
...


첫 빌드는 릴리스 패키지를 다운로드 & 인스톨해야 하므로 꽤나 시간이 걸립니다.

하지만, 두번째 빌드부터는 첫번째 빌드에서 구축한 환경에서 이뤄지므로 금방 빌드를 할 수 있습니다.


만약 빌드를 진행하다가 멈춘다면,

$ gbs build -A armv7l --clean --clean-repos

설치도중 에러가 발생한 것일 수도 있으니,

clean으로 깨끗이 정리한 뒤 다시 빌드를 시도해보세요.


빌드를 성공하면 아래와 같은 메시지를 볼 수 있습니다.

...
info: finished building starter
info: updating local repo
info: *** Build Status Summary ***
=== Total succeeded built packages: (1) ===
info: generated html format report:
     /home/user/GBS-ROOT/local/repos/tizen/i586/index.html
info: generated RPM packages can be found from local repo:
     /home/user/GBS-ROOT/local/repos/tizen/i586/RPMS
info: generated source RPM packages can be found from local repo:
     /home/user/GBS-ROOT/local/repos/tizen/i586/SRPMS
info: build logs can be found in:
     /home/user/GBS-ROOT/local/repos/tizen/i586/logs
info: build roots located in:
     /home/user/GBS-ROOT/local/BUILD-ROOTS/scratch.i586.*
info: Done


위에서 굵은 글씨로 표시되어있는 장소가,

빌드 결과물인 rpm 파일이 저장되어 있는 위치입니다.


빌드를 해보았으니,

conf 파일을 자세히 살펴보아 자신의 환경에 맞게 설정해보도록 하죠.


conf 파일은 총 4개의 섹션으로 이뤄져 있습니다.

[general] / [profile] / [obs] / [repo]

위의 4가지 섹션을 각각 살펴보겠습니다.

개인적으로 사용하는  .gbs.conf 파일을 붙여놓겠습니다.

gbs.conf

[general]
#upstream_branch = upstream
packaging_branch = tizen_2.3
buildroot = ~/GBS-ROOT/
packaging_dir = packaging
#Current profile name which should match a profile section name
profile = profile.mobile
#tmpdir = /var/tmp
#work_dir = .


- upstream_branch = master

  $ gbs clone 명령어로 다운로드 받을 upstream branch를 지정할 수 있습니다.

  git에서 master는 기본으로 다운로드 받으니 위의 명령어는 주석처리해도 무방합니다.

  실질적으로 쓸모가 없고, 최신 gbs에서 에러를 뱉네요.


- packaging_branch

  $ gbs clone 명령어로 git을 클론 받을 수도 있습니다.

  clone을 받은 후에 checkout 되는 브랜치를 general에서 미리 지정할 수 있습니다.


- buildroot

   빌드를 수행하기 위해 필요한 환경은 buildroot 디렉토리에 구축됩니다.

   각 프로파일별로도 buildroot를 만들 수 있습니다.

   만약 프로파일에서 설정한 buildroot가 없다면 general 섹션을 참고하게 됩니다.


- packaging_dir

  rpm패키징을 위해 설정하는 spec파일의 위치를 지정할 수 있습니다.

  default는 root/packaging이니, packaging으로 설정해두면 됩니다.


- profile

   타이젠은 멀티 프로파일을 지향하는 플랫폼입니다.

   모바일/웨어러블/자동차 등을 타겟으로 하고 있죠.

   따라서 빌드도 각 프로파일별로 수행할 수 있습니다.

   Default로 빌드할 프로파일은 위의 프로파일에서 지정할 수 있습니다.

   하지만, 빌드타임에 프로파일을 변경하고자 하면,

   $ gbs build --profile=profile.wearable -A i586

   위와 같이 --profile 옵션을 사용해야 합니다.


- tmpdir / work_dir

  general에는 변수선언이 가능합니다.

  다른 섹션에서 ${tmpdir}이나 ${work_dir}과 같이 변수를 사용할 수 있습니다.


[profile.mobile]
obs = obs.tizen
repos = repo.tizen_latest
# If no buildroot for profile, the buildroot in general section will be used
#buildroot = ~/GBS-ROOT-profile.tizen/
# Specify build conf for a specific profile by using shell-style variable references
#buildconf = ${work_dir}/tizen-conf/build.conf
# Specify a list of packages that don't participate in the building, which
# can also be used to break dependency circle.
#exclude_packages=filesystem,aul,libmm-sound,libtool
# Common authentication information
#user = xxx
#passwd = xxx


- obs

  프로파일을 remote에서 빌드하고자 할 때 참고하고, 

  obs 섹션과 동일한 이름을 넣어야 합니다.


- repos

  타이젠 플랫폼 패키지들이 있는 위치가 명시되어 있는 repos 섹션을 넣습니다.


- buildroot

  프로파일마다 buildroot를 다르게 지정하고자 하는 경우 입력합니다.

  그렇지 않으면 [general]에서 설정한 값이 설정됩니다.


- exclude_packages

  다수의 패키지를 빌드하고자 할때 빌드풀에 포함시키지 않을 패키지를 넣습니다.


- user / passwd

  repo.에 접근할 때 권한이 필요한 경우 넣어줍니다.

  repo.별로도 설정할 수 있습니다.


[obs.tizen]
url = https://api.tizen.org
user = xxx
passwd = xxx
# set default base_prj for this obs
#base_prj=Tizen:Main
# set default target prj for this obs, default is home:<user>:gbs:<base_prj>
#target_prj=<specify target project>


- url

  remote 빌드를 하기 위한 주소를 입력합니다.


- user / passwd

  remote 서버에 권한이 필요하다면 입력해주어야 합니다.


- base_prj

  remote 서버에서 빌드하고자 하는 패키지가 속한 base 프로젝트를 선택해둡니다.


- target_prj

  패키지가 장차 체크인될 프로젝트를 선택할 수도 있습니다.

  통상 base 프로젝트와 동일한 프로젝트를 입력합니다만,

  베이스 따로 타겟 따로 지정할 수도 있습니다.


[repo.tizen_2.1]
url = http://download.tizen.org/releases/2.1/latest/
#Optional user and password, set if differ from profile's user and password
#user =
#passwd =


- url

   url은 빌드를 수행하기 위해 dependency가 있는 모든 rpm이 다운로드 받을 수 있는 위치를 지정해야 합니다.

   로컬의 절대경로일 수도 있고, 인터넷 주소일 수도 있습니다.


- user / passwd

   위의 url에 접근하기 위해 별도의 권한이 필요한 경우 지정할 수 있습니다.

   타이젠은 오픈이기 때문에 user / passwd를 설정해줄 필요가 없습니다.


이상으로 gbs에 대한 설명을 마치겠습니다.

gbs를 사용하는 분들께 도움이 되면 좋겠습니다. :)


끝_


* References

https://source.tizen.org/documentation/reference/git-build-system?langredirect=1

https://source.tizen.org/documentation/reference/git-build-system/configuration-file

https://source.tizen.org/documentation/reference/git-build-system/usage/gbs-build?langredirect=1



타이젠 SDK만으로는,

타이젠 플랫폼 자체를 개발하기에는 아무래도 어려움이 있습니다.

타이젠 SDK에서는 타이젠의 모든 피쳐를 사용할 수 없으니까요.

Public으로 제한된 피쳐만 사용할 수 있지요.


따라서 플랫폼 자체를 개발하기 위해서는,

SDK 환경에서 벗어나,

- Private 피쳐를 포함하여 소스를 빌드하고

- 타겟에 설치할 툴체인이 필요합니다.


타이젠 플랫폼은 그러한 개발툴마저 오픈하였기에,

누구나 다운로드 받아 사용할 수 있습니다.



안녕하세요, Tizen 개발자 윤진입니다.


본 포스팅은 타이젠 플랫폼을 개발하기 위한 개발자를 위한 포스팅입니다.

타이젠 앱을 개발하려면 타이젠 SDK만으로도 충분하죠~


우분투 15.04버전에서 gbs 설치시 발생하는 문제는 아래 포스팅을 참고해주세요.

[Tizen] 우분투 15.04에서 gbs 실행에러 처리


gbs에 대한 추가정보는 아래 포스팅을 참고해주세요.

[Tizen] 타이젠 gbs의 모든 것


타이젠 플랫폼의 툴체인은 아래 주소에서 받을 수 있습니다.

http://download.tizen.org/tools/latest-release/


- CentOS 6

- Debian 7

- Fedora 19

- Fedora 20

- Ubuntu 12.04

- Ubuntu 14.04

- Ubuntu 14.10

- openSUSE 12.3

- openSUSE 13.1

위의 리스트에서 자신에 맞는 OS를 선택하시면 여러 툴 파일을 보실 수 있습니다.


물론, 여기서 직접 다운로드 받아도 되겠지만,

(우분투의 경우) 우분투 패키징 시스템으로 관리하는게 낫겠지요?

다른 패키지들과 함께 업데이트도 하고 말이죠~


그렇게 하기 위해서는 APT 툴로 패키지를 설치할 수 있도록

APT 소스 리스트에 타이젠 다운로드 사이트를 추가해주어야 합니다.


- Cent OS 6

$ cat /etc/apt/sources.list.d/tizen.list
deb http://download.tizen.org/tools/latest-release/CentOS_6/ /


- Debian 7

$ cat /etc/apt/sources.list.d/tizen.list
deb http://download.tizen.org/tools/latest-release/Debian_7/ /


- Fedora 19

$ cat /etc/apt/sources.list.d/tizen.list
deb http://download.tizen.org/tools/latest-release/Fedora_19/ /


- Fedora 20

$ cat /etc/apt/sources.list.d/tizen.list
deb http://download.tizen.org/tools/latest-release/Fedora_20/ /


- Ubuntu 12.04

$ cat /etc/apt/sources.list.d/tizen.list
deb http://download.tizen.org/tools/latest-release/Ubuntu_12.04/ /


- Ubuntu 14.04

$ cat /etc/apt/sources.list.d/tizen.list
deb http://download.tizen.org/tools/latest-release/Ubuntu_14.04/ /


- Ubuntu 14.10

$ cat /etc/apt/sources.list.d/tizen.list
deb http://download.tizen.org/tools/latest-release/Ubuntu_14.10/ /


- openSUSE 12.3

$ cat /etc/apt/sources.list.d/tizen.list
deb http://download.tizen.org/tools/latest-release/openSUSE_12.3/ /


- openSUSE 13.1

$ cat /etc/apt/sources.list.d/tizen.list
deb http://download.tizen.org/tools/latest-release/openSUSE_13.1/ /


위와 같이 소스 리스트에 타이젠 다운로드 사이트를 추가해주면 됩니다.

혹은 아래 파일을 다운로드 받으셔서 /etc/apt/sources.list.d/ 에 설치해주세요.

tizen.list

그리고 소스 리스트를 아래처럼 갱신해보죠.


$ sudo apt-get update
무시http://kr.archive.ubuntu.com trusty InRelease
무시http://kr.archive.ubuntu.com trusty-updates InRelease                     
무시http://kr.archive.ubuntu.com trusty-backports InRelease                   
기존 http://kr.archive.ubuntu.com trusty Release.gpg                          
기존 http://kr.archive.ubuntu.com trusty-updates Release.gpg                  
기존 http://kr.archive.ubuntu.com trusty-backports Release.gpg                
기존 http://kr.archive.ubuntu.com trusty Release                              
기존 http://kr.archive.ubuntu.com trusty-updates Release                      
기존 http://kr.archive.ubuntu.com trusty-backports Release                    
기존 http://kr.archive.ubuntu.com trusty/main Sources                         
기존 http://kr.archive.ubuntu.com trusty/restricted Sources                   
기존 http://kr.archive.ubuntu.com trusty/universe Sources                     
기존 http://kr.archive.ubuntu.com trusty/multiverse Sources                   
기존 http://kr.archive.ubuntu.com trusty/main i386 Packages                   
기존 http://kr.archive.ubuntu.com trusty/restricted i386 Packages             
기존 http://kr.archive.ubuntu.com trusty/universe i386 Packages               
기존 http://kr.archive.ubuntu.com trusty/multiverse i386 Packages             
무시http://download.tizen.org  InRelease                                      
기존 http://kr.archive.ubuntu.com trusty/main Translation-ko                  
기존 http://kr.archive.ubuntu.com trusty/main Translation-en                  
무시http://security.ubuntu.com trusty-security InRelease                      
기존 http://kr.archive.ubuntu.com trusty/multiverse Translation-en            
기존 http://kr.archive.ubuntu.com trusty/restricted Translation-ko            
기존 http://kr.archive.ubuntu.com trusty/restricted Translation-en            
기존 http://kr.archive.ubuntu.com trusty/universe Translation-ko              
기존 http://kr.archive.ubuntu.com trusty/universe Translation-en              
기존 http://kr.archive.ubuntu.com trusty-updates/main Sources                 
무시http://download.tizen.org  Release.gpg                                    
기존 http://kr.archive.ubuntu.com trusty-updates/restricted Sources           
기존 http://kr.archive.ubuntu.com trusty-updates/universe Sources             
기존 http://kr.archive.ubuntu.com trusty-updates/multiverse Sources           
기존 http://kr.archive.ubuntu.com trusty-updates/main i386 Packages           
기존 http://kr.archive.ubuntu.com trusty-updates/restricted i386 Packages     
기존 http://kr.archive.ubuntu.com trusty-updates/universe i386 Packages       
기존 http://kr.archive.ubuntu.com trusty-updates/multiverse i386 Packages     
기존 http://kr.archive.ubuntu.com trusty-updates/main Translation-en          
기존 http://kr.archive.ubuntu.com trusty-updates/multiverse Translation-en    
받기:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]         
기존 http://kr.archive.ubuntu.com trusty-updates/restricted Translation-en    
기존 http://kr.archive.ubuntu.com trusty-updates/universe Translation-en      
기존 http://kr.archive.ubuntu.com trusty-backports/main Sources               
기존 http://kr.archive.ubuntu.com trusty-backports/restricted Sources         
기존 http://kr.archive.ubuntu.com trusty-backports/universe Sources           
받기:2 http://download.tizen.org  Release [1,031 B]                           
기존 http://kr.archive.ubuntu.com trusty-backports/multiverse Sources         
기존 http://kr.archive.ubuntu.com trusty-backports/main i386 Packages         
무시http://extras.ubuntu.com trusty InRelease                                 
기존 http://kr.archive.ubuntu.com trusty-backports/restricted i386 Packages   
기존 http://kr.archive.ubuntu.com trusty-backports/universe i386 Packages     
기존 http://kr.archive.ubuntu.com trusty-backports/multiverse i386 Packages   
기존 http://kr.archive.ubuntu.com trusty-backports/main Translation-en        
기존 http://kr.archive.ubuntu.com trusty-backports/multiverse Translation-en  
기존 http://kr.archive.ubuntu.com trusty-backports/restricted Translation-en  
기존 http://kr.archive.ubuntu.com trusty-backports/universe Translation-en    
받기:3 http://security.ubuntu.com trusty-security Release [63.5 kB]           
무시http://kr.archive.ubuntu.com trusty/main Translation-ko_KR                
무시http://kr.archive.ubuntu.com trusty/multiverse Translation-ko_KR          
무시http://kr.archive.ubuntu.com trusty/multiverse Translation-ko             
무시http://kr.archive.ubuntu.com trusty/restricted Translation-ko_KR          
무시http://kr.archive.ubuntu.com trusty/universe Translation-ko_KR            
기존 http://extras.ubuntu.com trusty Release.gpg                              
기존 http://extras.ubuntu.com trusty Release                                  
받기:4 http://security.ubuntu.com trusty-security/main Sources [83.9 kB]      
기존 http://extras.ubuntu.com trusty/main Sources                             
기존 http://extras.ubuntu.com trusty/main i386 Packages                       
받기:5 http://security.ubuntu.com trusty-security/restricted Sources [2,061 B]
받기:6 http://security.ubuntu.com trusty-security/universe Sources [25.2 kB]  
받기:7 http://security.ubuntu.com trusty-security/multiverse Sources [2,333 B]
받기:8 http://security.ubuntu.com trusty-security/main i386 Packages [270 kB] 
받기:9 http://download.tizen.org  Packages [12.1 kB]                          
받기:10 http://security.ubuntu.com trusty-security/restricted i386 Packages [8,846 B]
받기:11 http://security.ubuntu.com trusty-security/universe i386 Packages [107 kB]
받기:12 http://security.ubuntu.com trusty-security/multiverse i386 Packages [3,840 B]
무시http://download.tizen.org  Translation-ko_KR                              
기존 http://security.ubuntu.com trusty-security/main Translation-en           
무시http://download.tizen.org  Translation-ko                                 
기존 http://security.ubuntu.com trusty-security/multiverse Translation-en     
무시http://download.tizen.org  Translation-en                                 
기존 http://security.ubuntu.com trusty-security/restricted Translation-en     
기존 http://security.ubuntu.com trusty-security/universe Translation-en       
무시http://extras.ubuntu.com trusty/main Translation-ko_KR                    
무시http://extras.ubuntu.com trusty/main Translation-ko           
무시http://extras.ubuntu.com trusty/main Translation-en           
내려받기 580 k바이트, 소요시간 7초 (80.6 k바이트/초)                          
패키지 목록을 읽는 중입니다... 완료


중간에 http://download.tizen.org도 보이네요.

업데이트가 완료되었다면,

이제 타이젠 플랫폼 툴인 gbs와 sdb를 설치할 수 있습니다.


$ sudo apt-get install gbs sdb
패키지 목록을 읽는 중입니다... 완료
의존성 트리를 만드는 중입니다      
상태 정보를 읽는 중입니다... 완료
다음 패키지가 자동으로 설치되었지만 더 이상 필요하지 않습니다:
  cli-common libicsharpcode-nrefactory-cecil5.0-cil
  libicsharpcode-nrefactory-csharp5.0-cil libicsharpcode-nrefactory5.0-cil
  libmono-cecil-private-cil libmono-corlib2.0-cil libmono-corlib4.5-cil
  libmono-i18n-west2.0-cil libmono-i18n-west4.0-cil libmono-i18n4.0-cil
  libmono-posix2.0-cil libmono-posix4.0-cil libmono-security2.0-cil
  libmono-security4.0-cil libmono-system-configuration4.0-cil
  libmono-system-core4.0-cil libmono-system-security4.0-cil
  libmono-system-xml4.0-cil libmono-system2.0-cil libmono-system4.0-cil
  mono-4.0-gac mono-gac mono-runtime mono-runtime-common mono-runtime-sgen
Use 'apt-get autoremove' to remove them.
다음 패키지를 더 설치할 것입니다:
  build createrepo debugedit deltarpm depanneur gbs-api gbs-export
  gbs-remotebuild git-buildpackage-common git-buildpackage-rpm
  libcommon-sense-perl libcrypt-ssleay-perl libencode-locale-perl
  libfile-listing-perl libfont-afm-perl libhtml-form-perl libhtml-format-perl
  libhtml-parser-perl libhtml-tagset-perl libhtml-template-perl
  libhtml-tree-perl libhttp-cookies-perl libhttp-daemon-perl libhttp-date-perl
  libhttp-message-perl libhttp-negotiate-perl libio-html-perl libjson-perl
  libjson-xs-perl liblwp-mediatypes-perl liblwp-protocol-https-perl
  libnet-http-perl librpm-tizen librpmbuild3 librpmsign1 libsqlite0
  libtext-unidecode-perl libwww-perl libwww-robotrules-perl libxdelta2
  libxml-parser-perl libyaml-libyaml-perl libyaml-perl osc pbzip2 pristine-tar
  python-deltarpm python-keyring python-lzma python-m2crypto python-pycurl
  python-rpm python-secretstorage python-sqlite python-sqlitecachec
  python-urlgrabber qemu-arm-static rpm texi2html xdelta yum
제안하는 패키지:
  rpm-i18n zipmerge libdata-dump-perl libipc-sharedcache-perl
  libauthen-ntlm-perl libyaml-shell-perl python-gdata python-keyczar
  python-kde4 libcurl4-gnutls-dev python-pycurl-dbg python-secretstorage-doc
  python-sqlite-dbg alien elfutils latex2html
다음 새 패키지를 설치할 것입니다:
  build createrepo debugedit deltarpm depanneur gbs gbs-api gbs-export
  gbs-remotebuild git-buildpackage-common git-buildpackage-rpm
  libcommon-sense-perl libcrypt-ssleay-perl libencode-locale-perl
  libfile-listing-perl libfont-afm-perl libhtml-form-perl libhtml-format-perl
  libhtml-parser-perl libhtml-tagset-perl libhtml-template-perl
  libhtml-tree-perl libhttp-cookies-perl libhttp-daemon-perl libhttp-date-perl
  libhttp-message-perl libhttp-negotiate-perl libio-html-perl libjson-perl
  libjson-xs-perl liblwp-mediatypes-perl liblwp-protocol-https-perl
  libnet-http-perl librpm-tizen librpmbuild3 librpmsign1 libsqlite0
  libtext-unidecode-perl libwww-perl libwww-robotrules-perl libxdelta2
  libxml-parser-perl libyaml-libyaml-perl libyaml-perl osc pbzip2 pristine-tar
  python-deltarpm python-keyring python-lzma python-m2crypto python-pycurl
  python-rpm python-secretstorage python-sqlite python-sqlitecachec
  python-urlgrabber qemu-arm-static rpm texi2html xdelta yum
다음 패키지를 업그레이드할 것입니다:
  sdb
1개 업그레이드, 62개 새로 설치, 0개 제거 및 231개 업그레이드 안 함.
6,431 k바이트 아카이브를 받아야 합니다.
이 작업 후 28.6 M바이트의 디스크 공간을 더 사용하게 됩니다.
계속 하시겠습니까? [Y/n] Y


설치가 완료되면 바로 gbs & sdb를 사용할 수 있습니다.

$ gbs --version
gbs 0.23.2

$ sdb version
Smart Development Bridge version 2.2.47


즐거운 개발하시길...


끝_


  1. rubric 2015.10.06 15:38

    gbs 빌드 중에 아래 에러를 만나면,
    error: <gbs>Maybe you are using new designed repo, and please specify real RPM repo with repodata under it.
    https://lists.tizen.org/pipermail/dev/2014-June/003224.html
    에서 얘기하는 것처럼 build repo 를 더 정확하게 입력하면 해결되네요

    • Using gbs (>=0.16), if you still specify the top directory of repository like:
      http://download.tizen.org/snapshots/tizen/common/tizen_20140618.1/

      GBS will report error as follow:

      info: generate repositories ...
      error: <gbs>Maybe you are using new designed repo, and please specify real RPM
      repo with repodata under it.

      So the real standard repository should be used now, for example:
      http://download.tizen.org/snapshots/tizen/common/tizen_20140618.1/repos/ia32-wayland/packages/

      이런 내용이 있군요~!
      굿 정보 감사합니다!

  2. 김현호 2018.10.01 18:08

    리스트를 추가하고 update를 했는데
    홈페이지의 repository가 잘못되었다고 나오네요 ㅠㅠ
    홈페이지 문제인가요?

    the repository '주소' is not signed.

  3. ffff 2018.11.01 14:34

    sdb 설치하니까 다른 패키지가 설치되는것 같은데요

코딩을 하고 있으면,

으레 큰 벽을 만나 더 이상의 진척을 볼 수 없는 순간이 있습니다.


의지할 만한 멘토가 있다면,

커피를 한 잔 사들고 가서 답을 구걸하면 되지만...

조언을 해줄만한 사람이 없으면,

삽질이라는 최악의 방법으로 문제를 풀어나가는 수밖에 없죠.


안녕하세요, Tizen 개발자 윤진입니다.


앱을 개발하면 으레 수없이 많은 난관에 부딪힐 것입니다.

헤더파일을 꼼꼼히 살펴보고,

예제코드도 따라가 보지만,

결국 아무 소득이 없으면 소스코드를 하나씩 따라가보며 분석하는 수밖에 없죠.


하지만, 소스코드를 뒤지는 행위는,

굉장히 많은 노력과 시간을 필요로 합니다.


노력과 시간을 들여 해법을 얻으면야 좋겠지만,

세상사가 늘 그렇게 마음먹은 대로 되지 않기 마련이죠.


그럴 때 필요한 것이 포럼입니다.

포럼에는 이미 삽질을 경험했거나 관련 코드에 대해 잘 아는 사람이 '분명' 있습니다.

다행히 타이젠에도 쓸만한 포럼이 있네요.


1. 타이젠 개발자 포럼 : https://developer.tizen.org/forums

타이젠 플랫폼 차원에서 운영하는 포럼입니다.



4개의 카테고리가 준비되어 있습니다.

- General Support : 온갖 종류의 질문과 노하우 공유

- Web Application Development : 웹앱 개발과 관련된 질의응답

- Native Application Development : 네이티브앱 개발과 관련된 질의응답

- SDK & IDE : 개발환경과 관련된 질의응답


질문을 올리거나 답변을 하려면 우선 로그인을 해야합니다.

로그인 후에 질문/답변을 위한 카테고리에 들어갑니다.


질문을 하기 위해서는,

상단에 노출되어 있는 New topic을 누르면 됩니다.



버튼을 누르면 질문을 할 수 있게 화면이 준비가 됩니다.

Subject에 글제목을 간단하게 적고,

Forums에 위의 네 카테고리 중 하나를 선택합니다.

그리고 아래 표에 질문 내용을 언급하면 됩니다.



질문을 올리면 반드시 답변이 달립니다.

최근에 올라온 질문 외에 답변이 0개인 질문을 찾다가 실패했습니다. :)


글을 읽다가 답변을 달 수도 있습니다.

글 하단부에 달려있는 REPLY 버튼을 누르거나 REPLY TO THIS THREAD에 답변을 달면 됩니다.



알고 있는 지식을 나눠주세요.

당신의 지식이라도 상대에게는 큰 도움이 될 것입니다.


2. 삼성 개발자 포럼 : http://developer.samsung.com/forum/en

삼성 개발자 포럼을 이용할 수도 있습니다.

삼성 개발자 포럼에서는 타이젠 외의 문의/답변도 있습니다.

포럼 상단에 카테고리를 보면 무려 30여개의 카테고리가 구비되어 있네요.

여기서 타이젠에 직접적으로 관련이 있는 것은 Gear 카테고리 하나입니다.



삼성 개발자 포럼에서도 질문/답변을 하려면 로그인을 해야합니다.

로그인을 하고 위의 이미지 상단 오른편에 있는 New Thread 버튼을 눌러 질문을 해봅시다.



질문 내용을 먼저 채우고,

카테고리를 그에 맞게 선택합니다.

태그를 달아놓으면 차후에 비슷한 의문을 가진 사람이 태그로 쉽게 검색할 수 있겠죠?

첨부파일이 필요하면, Upload Files를 이용하세요.


기존에 있는 질문에 대답을 하려면,

질문 하단에 있는 Post Reply 버튼을 누르면 됩니다.


정리해보자면,

타이젠 SDK를 사용하며 궁금한 부분이 생기면 타이젠 포럼을 찾고,

기어 SDK를 사용하며 궁금한 부분이 생기면 삼성 포럼을 찾아가시면 됩니다. 


대답은 둘 다 꼬박꼬박 달리는 편이라,

궁금증을 금방 풀 수 있을 겁니다.


건투를 빕니다. :)


끝_




한밤 중 1시 51분,

잘까 말까 잠시 망설이다가,

DB에 대한 포스팅을 마무리하기 위해 세수를 하고 왔습니다.


이 포스팅에는 대단한 스키마가 나오는 것도 아니고,

어마무시한 쿼리문도 나오지 않습니다.


그저 비몽사몽 간에,

앱단에서 사용할만한 함수 몇가지를 공유하고자 합니다.


타이젠에서 공식적으로 지원하는 DB는 SQLite3입니다.

(참고, "Tizen 플랫폼 DB 엿보기-", http://storycompiler.tistory.com/25)

오래전부터 SQLite였고 그 기조는 당분간 바뀌지 않을겁니다.

따라서 SQLite를 다른 DB로 포팅할 일도 없겠군요.


그렇지만, 순전히 앱사이드에서 sqlite3를 덕지덕지 소스에 붙여 놓고 싶지도 않습니다.

sqlite3_open() / sqlite3_close()를 매번 수행하며,

그 때마다 온갖 예외처리를 하다보면,

분명 비슷한 루틴이 반복될 것이고,

그러는 와중에 버그라는 친숙한 녀석을 만나게 될겁니다.


따라서 sqlite3를 앱 내부 함수에 캡슐로 감춰서

조금이라도 노출을 막고,

조금이라도 쉽게 사용하고자 합니다.


static struct {
    sqlite3 *db;
} db_info = {
    .db = NULL,
};


우선, sqlite3_open()으로 나오는 db 핸들은 static 전역 변수로 선언해볼까요?

전역 변수는 매우 위험하여 가급적이면 사용하진 않지만,

- 개발하고자 하는 앱은 오직 한 프로세스에서만 사용하고,

- 단일스레드로 동작하며,

- reentrant 따위는 일어날리도 없기에,

단일 파일에서만 접근할 수 있는 static 전역 변수로 선언하곤 합니다.


이로써 모든 함수의 첫번째 인자(sqlite3 *db)는 생략할 수 있게 된 셈이지요.

덕분에 API 사용이 간단해졌습니다.

하지만, 이러한 제한적인 상황 외에 전역변수를 남용한다면,

숨겨진 버그를 중요한 순간마다 튀어나와 정신건강에 치명적인 독이 되고 맙니다. :)

특히, 라이브러리에서 전역변수는 고민에 고민을 거듭하여 제거하는게 좋겠지요.


db_open()은 이전 포스팅에서 설명한 바 있습니다.

Tizen 플랫폼에서 앱의 데이터 저장공간을 app_get_data_path()로 얻어와 DB 파일을 저장합니다.

(참고, "Tizen 앱 DB는 언제 어디에 초기화할까", http://storycompiler.tistory.com/29)


HAPI appl_error_e db_open(void)
{
    char *path = NULL;
    char db_file[FILE_LEN] = {0, };
    int ret = SQLITE_OK;

    path = app_get_data_path();
    retv_if(!path, APPL_ERROR_FAIL);
   
    snprintf(db_file, sizeof(db_file), "%s/%s", path, APP_DB_FILE);

    ret = sqlite3_open(db_file, &db_info.db);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        free(path);
        return APPL_ERROR_FAIL;
    }

    free(path);
    return APPL_ERROR_NONE;
}

HAPI void db_close(void)
{
    if (!db_info.db) {
        _D("DB is already NULL");
        return;
    }

    sqlite3_close(db_info.db);
    db_info.db = NULL;
}


db_open()과 db_close()는 db 핸들을 전역으로 뽑아놨기 때문에 함수 패러미터도 없습니다.

따라서 원하는 시점에 인자에 대한 고민없이 open / close 하면 됩니다.

편하군요~


HAPI sqlite3_stmt *db_prepare(const char *query)
{
    sqlite3_stmt *stmt = NULL;
    int ret = SQLITE_OK;

    retv_if(!query, NULL);

    ret = sqlite3_prepare_v2(db_info.db, query, strlen(query), &stmt, NULL);
    if (SQLITE_OK != ret) {
        _E("%s, %s", query, sqlite3_errmsg(db_info.db));
        return NULL;
    }

    return stmt;
}
HAPI appl_error_e db_next(sqlite3_stmt *stmt)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);

    ret = sqlite3_step(stmt);
    switch (ret) {
    case SQLITE_ROW:
        return APPL_ERROR_NONE;
    case SQLITE_DONE:
        return APPL_ERROR_NO_DATA;
    default:
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}
HAPI appl_error_e db_reset(sqlite3_stmt *stmt)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_INVALID_PARAMETER);

    ret = sqlite3_reset(stmt);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    sqlite3_clear_bindings(stmt);

    return APPL_ERROR_NONE;
}


db_prepare(), db_next(), db_reset()은 sqlite3_prepare_v2(), sqlite3_next(), sqlite3_reset() 기능을 수행하기 위한 함수입니다.

우선, db_prepare()에서 패러미터로 전달받은 쿼리문을 파싱하여 sqlite3 *stmt를 리턴하죠.

stmt는 db_next()에 인자로 들어가서 실행됩니다.

db_reset()에서는 stmt를 재사용하기 위해 bind된 인자가 있으면 clear 합니다.

반드시 clear를 하고 다시 bind를 해야합니다.

어렴풋 reset을 제대로 하지 않아서 삽질을 했던 기억이 떠오르네요.

여기서 bind는 이어서 설명하는 db_bind_xxxx()함수와 연결됩니다.


HAPI appl_error_e db_bind_bool(sqlite3_stmt *stmt, int idx, bool value)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);

    ret = sqlite3_bind_int(stmt, idx, (int) value);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}
HAPI appl_error_e db_bind_int(sqlite3_stmt *stmt, int idx, int value) {
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);

    ret = sqlite3_bind_int(stmt, idx, value);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}
HAPI appl_error_e db_bind_double(sqlite3_stmt *stmt, int idx, double value)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);

    ret = sqlite3_bind_double(stmt, idx, value);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}
HAPI appl_error_e db_bind_str(sqlite3_stmt *stmt, int idx, const char *str)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);
    retv_if(!str, APPL_ERROR_FAIL);

    ret = sqlite3_bind_text(stmt, idx, str, strlen(str), SQLITE_TRANSIENT);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}


바인드는 db_prepare()에서 파싱한 쿼리문 중에 '?'로 처리한 부분에 치환되어 들어갑니다.

문자열 쿼리문을 더 빨리 처리하기 위해,

확정된 부분은 db_prepare()로 먼저 파싱하고,

확정할 수 없는 부분은 sqlite3_bind_xxxx()로 차후에 입력하게 됩니다.


예를 들어,

query = "select sequence from bookmarks where parent=? order by sequence desc";

(참고, git://review.tizen.org/apps/web/browser, src/database/browser-bookmark-db.cpp)

위와 같은 쿼리문의 where 절에 '?'로 처리된 부분은,

sqlite3_bind_int로 차후에 채워넣게 됩니다.


단, 항상 헛갈리는 부분인데 bind에 들어가는 index는 0이 아닌 1로 시작합니다. 응?

위의 예에서 parent의 '?'에 바인드하기 위해서는 '0'이 아니라 '1'을 index로 넣어야 합니다.

오래 전에 SQLite 서적을 보며 1부터 시작하는 납득할 만한 이유를 찾았던 것으로 기억하는데,

더 이상 기억이 나지 않는 것으로 보아 충분히 납득하지 못했었나 봅니다.


HAPI bool db_get_bool(sqlite3_stmt *stmt, int index)
{
    retv_if(!stmt, false);
    return (bool) sqlite3_column_int(stmt, index);
}
HAPI int db_get_int(sqlite3_stmt *stmt, int index)
{
    retv_if(!stmt, 0);
    return sqlite3_column_int(stmt, index);
}
HAPI int db_get_double(sqlite3_stmt *stmt, int index)
{
    retv_if(!stmt, 0);
    return sqlite3_column_double(stmt, index);
}
HAPI const char *db_get_str(sqlite3_stmt *stmt, int index)
{
    retv_if(!stmt, NULL);
    return (const char *) sqlite3_column_text(stmt, index);
}


위의 함수군은 select 쿼리의 결과로 나온 값들을 얻기 위해 사용합니다.

index는 0부터 차례대로 카운팅됩니다.

bind처럼 1부터 시작하지 않고 0부터 시작하니 헛갈리지 마세요.


HAPI appl_error_e db_finalize(sqlite3_stmt *stmt)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_INVALID_PARAMETER);

    ret = sqlite3_finalize(stmt);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}


stmt를 더 이상 사용할 필요가 없다면 db_finalize를 해야합니다.

이로써 db_prepare()에서 생성된 stmt의 라이프사이클이 드디어 종료됩니다.

db_prepare()로 핸들을 생성하였으면,

반드시 db_finalize()까지 진행해주세요.


HAPI appl_error_e db_exec(const char *query)
{
    sqlite3_stmt *stmt = NULL;

    retv_if(!query, APPL_ERROR_INVALID_PARAMETER);

    stmt = db_prepare(query);
    retv_if(!stmt, APPL_ERROR_FAIL);

    goto_if(APPL_ERROR_FAIL == db_next(stmt), ERROR);
    goto_if(APPL_ERROR_FAIL == db_finalize(stmt), ERROR);

    return APPL_ERROR_NONE;

ERROR:
    if (stmt) db_finalize(stmt);
    return APPL_ERROR_FAIL;
}


db_exec()는 create / drop / insert / update / delete 처럼,

유의미한 실행결과를 얻을 필요가 없는 쿼리문에 사용합니다.


쿼리문과 db_exec()만 사용하면 얼마든지 원하는 쿼리문을 수행할 수 있습니다.

하지만, 너무나 쉽게 사용할 수 있다보니 해킹의 위험에 노출될 수도 있죠.

개발자가 최초에 쿼리문을 하나 만들어 sqlite3_exec()에 넣었다고 해보죠.

해커가 쿼리문의 "%s" 같은 부분을 조작하여 다중 쿼리문으로 바꿔치기하면,

력된 다중 쿼리문이 sqlite3_exec()에 의해 모두 실행됩니다.

난리나게 되는 거죠.


따라서 언제나 오직 하나의 쿼리문만 실행할 수 있도록,

db_exec() 내부에서 prepare, next, finalize를 수행하도록 변경하였습니다.

next는 하나의 쿼리문만 수행하도록 설계가 되어 있답니다.


HAPI appl_error_e db_begin_transaction(void)
{
    int ret = SQLITE_BUSY;

    while (1) {
        ret = sqlite3_exec(db_info.db, "BEGIN IMMEDIATE TRANSACTION", NULL, NULL, NULL);
        if (SQLITE_BUSY != ret) {
            break;
        }
        /* FIXME : we have to fix this sleep */
        sleep(1);
    }

    if (SQLITE_OK != ret) {
        _E("sqlite3_exec() Failed(%d)", ret);
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}



HAPI appl_error_e db_end_transaction(void)
{
    int ret = SQLITE_OK;

    while (1) {
        ret = sqlite3_exec(db_info.db, "COMMIT TRANSACTION", NULL, NULL, NULL);
        if (SQLITE_BUSY != ret) {
            break;
        }
        /* FIXME : we have to fix this sleep */
        sleep(1);
    }

    if (SQLITE_OK != ret) {
        _E("sqlite3_exec() Failed(%d)", ret);
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}


트랜잭션이 필요할 수도 있겠네요.

BEGIN TRANSACTION - COMMIT TRANSACTION 사이에 벌어지는 디비 루틴은 한 번에 처리됩니다.


다만, 상기 함수에서 트랜잭션 쿼리를 수행할 때,

SQLITE_BUSY가 나는 경우 sleep()을 걸어놓았습니다.

sleep()은 프로세스 전체를 멈추게 할 수도 있으니 신중하게 사용해야 합니다.

프로세스의 디자인에 따라 이 부분은 각자의 mainloop()에 사용할 수 있는 timer 등으로 치환할 수도 있습니다.


이제는 정말 취침에 들어가야겠네요.

집중력이 바닥났습니다.


끝_


* 2015. 6. 2 정신차리고 일어나서 한밤중에 멋대로 휘갈긴 문장을 수정.


  1. developer 2016.06.29 16:12

    애정이 느껴지네요 잘보고 갑니다 ㅎㅎ

    • 안녕하세요, 개발자님. 애정은 넘치죠. 하하하. 제가 지금은 러시아에 나와 있어서 잠시 타이젠과 멀어져 있긴 한데... 언제나 애정만큼은 충만해있어요.

일요일 석양무렵,

관리비 용지 위에다가 앱을 위한 디비 스키마를 그렸습니다.


공동항목과 세대항목으로 나뉘어진 관리비 용지 가운데,

가장 큰 부분을 차지하는 주차비충당금을 보며 한숨을 쉬다가도,

디비에 새겨놓아야하는 필드가 떠오를때마다 다시 펜을 부여잡고 관리비 용지 귀퉁이에 스키마를 그렸죠.


테이블 여덟개를 끄적이고 나니

코드로 옮겨 확인을 해봐야겠다는 생각이 들었습니다.


안녕하세요, Tizen 개발자 윤진입니다.


앱을 위한 DB는 어느 시점에 초기화되어야할까요?

DB 초기화 시점은 아래 중에 하나일 것입니다.

- 앱이 설치되는 시점

- 앱이 처음 런칭되는 시점

- 앱에서 실질적으로 DB에 접근하는 시점


1. 앱이 설치되는 시점

앱이 설치되는 시점에 DB를 만들면,

앱을 런칭한 이후에 DB를 만들 필요가 없으므로,

런칭 혹은 런타임 중에 DB 초기화로 시간을 보내지 않아도 됩니다.


다만 멀티유저 시스템에서 하나의 앱을 다수의 사용자가 공유할 때,

각 사용자마다 DB 파일을 따로 관리하는 경우,

DB 파일을 런타임 중에 추가로 초기화해야합니다.


앱을 최초로 설치한 사용자는 앱을 설치하며 DB를 초기화하고,

앱을 최초로 설치하지 않은 사용자는 설치하며 DB를 초기화하지 못했기 때문에, 런타임 중에 초기화를 해야합니다.

이러한 루틴이 적합한 곳도 있을 수도 있지만,

일관성 있는 룰을 원하는 개발자는 다른 방식으로 DB를 초기화하고 싶겠죠?


2. 앱이 처음 런칭되는 시점

앱이 처음 런칭되는 시점에,

DB파일에 접근하여 DB파일이 준비되어 있지 않다면,

DB를 초기화하는 루틴을 추가할 수도 있습니다.


이 경우는 첫 런칭에서 DB를 초기화해야하기 때문에

첫 런칭 속도에 영향을 줄 수 있습니다.

사용자가 홈/메뉴에서 앱을 선택하였지만,

DB 초기화로 시간을 허비하다보면 최적화가 되어 있지 않은 앱으로 의심 받을 수 있죠. :)


하지만, 멀티유저 환경에서 유연하게 대응할 수 있습니다.

앱을 설치한 유저 외에 다른 유저가 앱을 런칭할 때,

홈 디렉토리에서 DB 파일을 찾아보고 DB 파일이 없으면 바로 생성하면 됩니다.

앱을 설치한 유저나 사용하는 유저나 모두 일관되게 앱을 런칭하며 DB를 만들게 되죠.


3. 앱에서 실질적으로 DB에 접근하는 시점

DB 초기화 루틴을 최대한 뒤로 미룬다.

DB에서 관리하는 데이터가 런칭과 무관하다면,

어렵지 않게 런칭 루틴에서 떼어내어 실제 DB가 사용되는 시점으로 초기화를 늦출 수 있습니다.

이 경우 런칭 성능에는 DB가 전혀 영향을 미치지 않는다는 장점이 있습니다.


다만 DB에 접근하는 루틴과 사용자 동작과 겹치게 된다면,

사용자는 앱이 순간적으로 멈춘다는 인상을 받을 수도 있겠죠.


위의 세 가지 모두 장단점이 있습니다.

모두의 장점을 취할 수는 없기 때문에,

여기서는 2번, 앱이 런칭되며 DB를 초기화하는 루틴을 선택하기로 하죠.

첫 런칭에서 DB 초기화로 약간의 시간을 소요하고 난 뒤,

앱에 진입하고 나서부터는 적어도 DB 때문에 사용자가 기다리지 않도록 하고 싶습니다.


그렇다면, DB 파일은 어디에 생성해야하는 것일까요?


Tizen 2.4에서 제공하는 API는,

앱의 데이터 영역을 확정하여 알려줍니다.



위의 함수에서 데이터를 저장할 디렉토리를 얻어온 후,

런칭 직후 위의 디렉토리에 DB가 없으면 바로 초기화 루틴으로 진입하면 됩니다.


그렇다면, 위의 함수는 멀티유저에 대비가 되어 있을까요?

현재 소스를 따라가 보면,

앱의 pkgid로 data 디렉토리를 한정짓고 있네요.


static int __get_root_path(char *root_path, int root_path_len, bool external)
{
    static char pkgid[_MAX_PACKAGE_ID_LEN] = {0,};
    const char *specific_path = external ? _EXTERNAL_APP_SPECIFIC_PATH : _APP_SPECIFIC_PATH;

    if (pkgid[0] == '\0')
    {  
        int err = __get_pkgid(pkgid, _MAX_PACKAGE_ID_LEN - 1);
        if (err != AUL_R_OK)
        {
            return err;
        }
    }  
    {  
        int specific_path_len = strlen(specific_path);
        int pkgid_len = strlen(pkgid);
        int total_len = specific_path_len + pkgid_len + 1;

        if (total_len > root_path_len)
        {
            _E("Assert: path length %d is too long", total_len);
            assert(false);
        }

        strncat(root_path, specific_path, specific_path_len);
        strncat(root_path + specific_path_len, pkgid, pkgid_len);
        root_path[specific_path_len + pkgid_len] = '/';
    }  

    return AUL_R_OK;
}


위의 함수를 사용하여,

아래와 같이 db_open()을 구현할 수 있습니다.


HAPI appl_error_e db_open(void)
{
    char *path = NULL;
    char db_file[FILE_LEN] = {0, };
    int ret = SQLITE_OK;

    path = app_get_data_path();
    retv_if(!path, APPL_ERROR_FAIL);
   
    snprintf(db_file, sizeof(db_file), "%s/%s", path, APP_DB_FILE);

    ret = sqlite3_open(db_file, &db_info.db);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        free(path);
        return APPL_ERROR_FAIL;
    }  

    free(path);
    return APPL_ERROR_NONE;
}


위처럼 app_get_data_path()로 data를 저장할 디렉토리를 얻어온 후,

DB 명을 대입하여 sqlite3_open()을 수행하면 되죠.


이제 막 DB 초기화와 관련된 걸음마를 떼었습니다.

앞으로 갈 길이 삼천리인데 또 눈꺼풀이 잠겨오네요.


끝_

  1. initor 2019.05.17 20:43 신고

    타이젠 블로그에서 많은 도움을 얻고 있습니다. 위 코드에서 HAPI appl_error_e 리턴형이나 APPL_ERROR_FAIL 등의 매크로는 어디에 정의된 것인지 모르겠습니다. __get_pkgid 등의 함수에 대해서도 알 수가 없습니다.

개발자로서 살아온 기간내내

함수 안팎으로 로그를 '신나게' 심어왔습니다.


코드에 로그가 없는 경우는,

- 아직 로그를 심기 직전이거나

- 동작을 확인하고 로그를 지운 뒤겠죠.


앱이 물고 있는 프레임워크의 동작이 언제나 완벽하다면,

앱에서 로그를 출력할 일은 크게 줄어들 것입니다.


하지만, 언제나 프레임워크는 개발막바지가 되어야 비로소 쓸만해지죠. :)

모든 함수에 에러체크는 필수이고,

에러로그는 가장 쉬우면서 확실한 디버깅 방법입니다.


안녕하세요, Tizen 개발자 윤진입니다.


타이젠은 플랫폼 차원에서 로그를 남길 수 있는 방법을 제공합니다.

printf(), fprintf()도 사용할 수 있지만,

- SDK에 로그를 노출하거나,

- 시스템 로그들과 함께 사용하기 위해서는,

타이젠에서 제공하는 dlog를 사용해야 합니다.


타이젠 개발자 사이트(http://developer.tizen.org)에 들어가서,

하단의 API References 메뉴에 들어갑니다.



왼편에 있는 프레임에서,

API References > Native Application > Native API Reference > System > dlog

위의 항목을 선택합니다.



플랫폼에서 지원하는 로그를 위한 함수는 2가지가 있습니다.

dlog_print()와 dlog_vprint()입니다.

위의 두 함수는 공통적으로 priority와 tag를 입력해주어야 합니다.


priority는 출력할 로그의 종류에 따라 선택하면 되죠.

개발자의 필요에 따라 사용하는 로그이므로,

priority 사용에 제한을 두진 않습니다.

(소스 출처 : git://review.tizen.org/framework/system/dlog, tizen_2.3)


 * @brief log priority values, in ascending priority order.
 * @since_tizen 2.3
 */
typedef enum {
    DLOG_UNKNOWN = 0, /**< Keep this always at the start */
    DLOG_DEFAULT, /**< Default */
    DLOG_VERBOSE, /**< Verbose */
    DLOG_DEBUG, /**< Debug */
    DLOG_INFO, /**< Info */
    DLOG_WARN, /**< Warning */
    DLOG_ERROR, /**< Error */
    DLOG_FATAL, /**< Fatal */
    DLOG_SILENT, /**< Silent */
    DLOG_PRIO_MAX   /**< Keep this always at the end. */
} log_priority;


tag는 일반적으로 자신의 모듈명을 입력합니다.

플랫폼에서 사용하는 tag는 대부분 대문자와 '_'(underscore)를 사용하고 있네요.


#define LOG_TAG "BUNDLE"

#define LOG_TAG "PKGMGR"

#define LOG_TAG "CAPI_APPFW_APPLICATION"

#define LOG_TAG "VOLUME"


하지만, 소문자만으로 태그이름을 정한 모듈도 있긴 합니다.

이를 보건대 태그이름에 대한 정책적인 제약은 없는 것으로 보이네요.

모듈로그는 태그명으로 검색할 수 있으므로 차후를 위해 잘 기억해두세요.


/**
 * @addtogroup CAPI_SYSTEM_DLOG
 * @{
 */
/**
 * @brief     Send log with priority and tag.
 * @details   for application
 * @since_tizen 2.3
 * @param[in] prio log_priority
 * @param[in] tag tag
 * @param[in] fmt format string
 * @return On success, the function returns the number of bytes written.
 *         On error, a negative errno-style error code
 * @retval #DLOG_ERROR_INVALID_PARAMETER Invalid parameter
 * @retval #DLOG_ERROR_NOT_PERMITTED Operation not permitted
 * @pre       none
 * @post      none
 * @see       dlog_vprint
 * @code
#include<dlog.h>
int main(void)
{
    int integer = 21;
    char string[] = "test dlog";

    dlog_print(DLOG_INFO, "USR_TAG", "test dlog");
    dlog_print(DLOG_INFO, "USR_TAG", "%s, %d", string, integer);
    return 0;
}
 * @endcode
 */
int dlog_print(log_priority prio, const char *tag, const char *fmt, ...);
/**
 * @brief     Send log with priority, tag and va_list.
 * @details   for application
 * @since_tizen 2.3
 * @param[in] prio log_priority
 * @param[in] tag tag
 * @param[in] fmt format string
 * @param[in] ap va_list
 * @return On success, the function returns the number of bytes written.
 *         On error, a negative errno-style error code
 * @retval #DLOG_ERROR_INVALID_PARAMETER Invalid parameter
 * @retval #DLOG_ERROR_NOT_PERMITTED Operation not permitted
 * @pre       none
 * @post      none
 * @see       dlog_print
 * @code
#include<dlog.h>
void my_debug_print(char *format, ...)
{
    va_list ap;

    va_start(ap, format);
    dlog_vprint(DLOG_INFO, "USR_TAG", format, ap);
    va_end(ap);
}

int main(void)
{
    my_debug_print("%s", "test dlog");
    my_debug_print("%s, %d", "test dlog", 21);
    return 0;
}
 * @endcode
 */
int dlog_vprint(log_priority prio, const char *tag, const char *fmt, va_list ap);


함수 정의를 살펴보면,

priority와 tag를 지정한 뒤,

로그로 출력하고자 하는 포맷을 넣어주면 됩니다.

두 함수 모두 내부적으로 동일한 루틴을 따르네요.


static int __write_to_log_sd_journal(log_id_t log_id, log_priority prio, const char *tag, const char *msg)
{
    pid_t tid = (pid_t)syscall(SYS_gettid);
    /* XXX: sd_journal_sendv() with manually filed iov-s might be faster */
    return sd_journal_send("MESSAGE=%s", msg,
                   "PRIORITY=%i", dlog_pri_to_journal_pri(prio),
                   "LOG_TAG=%s", tag,
                   "LOG_ID=%s", dlog_id_to_string(log_id),
                   "TID=%d", tid,
                   NULL);
}


dlog는 내부적으로 systemd의 journal을 사용합니다.

sd-journal(systemd's journal)은 systemd에서 관리하는 로그를 위한 서비스입니다.

dlog는 sd_journal_send()를 사용하여 sd-journal에 로그를 입력하고 있습니다.

sd-journal에 대한 이야기는 기회가 있을때 다시 하기로 하죠.


앱에서는 로그를 수도 없이 사용하므로,

간단히 사용할 수 있는 매크로 함수를 사용하기로 합니다.

실제 타이젠의 시스템 앱에서 사용하고 있는 매크로 함수가 있습니다.

시스템 앱에서 사용하고 있는 매크로 함수를,

SDK로 개발하는 앱에서 사용할 수 있도록 수정하였습니다.

(소스출처 : http://www.freedesktop.org/software/systemd/man/sd-journal.html, master)


#ifndef __YOUR_APPLICATION_LOG_H__
#define __YOUR_APPLICATION_LOG_H__

#include <dlog.h>

#undef LOG_TAG
#define LOG_TAG "YOUR_APPLICATION"

#define COLOR_RED "\033[0;40;31m"
#define COLOR_LIGHTBLUE "\033[1;40;34m"
#define COLOR_YELLOW "\033[1;40;33m"
#define COLOR_END "\033[0;m"
#define COLOR_GRAY "\033[1;40;30m"
#define COLOR_MAGENTA "\033[1;35m"

#if !defined(_D)
#define _D(fmt, arg...) dlog_print(DLOG_DEBUG, LOG_TAG, COLOR_YELLOW fmt COLOR_END"\n", ##arg)
#endif

#if !defined(_W)
#define _W(fmt, arg...) dlog_print(DLOG_WARN, LOG_TAG, COLOR_MAGENTA fmt COLOR_END"\n", ##arg)
#endif

#if !defined(_E)
#define _E(fmt, arg...) dlog_print(DLOG_ERROR, LOG_TAG, COLOR_RED fmt COLOR_END"\n", ##arg)
#endif

#define retv_if(expr, val) do { \
    if(expr) { \
        _E("(%s) -> %s() return", #expr, __FUNCTION__); \
        return (val); \
    } \
} while (0)

#define ret_if(expr) do { \
    if(expr) { \
        _E("(%s) -> %s() return", #expr, __FUNCTION__); \
        return; \
    } \
} while (0)

#define goto_if(expr, val) do { \
    if(expr) { \
        _E("(%s) -> goto", #expr); \
        goto val; \
    } \
} while (0)

#define break_if(expr) { \
    if(expr) { \
        _E("(%s) -> break", #expr); \
        break; \
    } \
}

#define continue_if(expr) { \
    if(expr) { \
        _E("(%s) -> continue", #expr); \
        continue; \
    } \
}

#endif                /* __YOUR_APPLICATION_LOG_H__ */


헤더를 사용하려면 아래 로그헤더를 다운로드 받으시면 됩니다.

log.h

헤더를 사용하시려면,

- LOG_TAG에 자신의 모듈이름을 적어주세요.

- 일반적인 디버그 상황에서 사용하는 디버그 로그는 _D 매크로 함수를 사용하고,

    _D("%s's GEOMETRY : [%d, %d, %d, %d]", (const char *) data, x, y, w, h);

- 에러는 아니지만, 일반적인 디버그 로그보다 위험한 경우는 워닝 로그 _W 매크로 함수를 사용하고,

    _W("cannot find index.");

- 에러 상황에서는 _E 매크로 함수를 사용하시면 됩니다.

    _E("Failed to remove vconf %s", "memory/menuscreen/mapbuf");


로그를 위한 장치가 준비되었으니,

이제 본격적으로 코딩을 하며 '신나게' 로그를 삽입하면 됩니다.

SDK > 하단 프레임 > Log

위의 메뉴와 친해지도록 하세요. :)


끝_

간단한 앱을 하나 짜더라도

사용자로그를 관리하기 위해서는 DB가 필요하죠.


하지만, 플랫폼마다 지원하는 DBMS가 달라서,

낯선 DBMS 환경에 적응해야하는 경우도 있습니다.


다행스럽게도 타이젠에서는 모바일이나 웨어러블 같은 임베디드 환경에서,

가장 널리 사용되고 있는 SQLite를 지원하고 있습니다.


안녕하세요, Tizen 개발자 윤진입니다.


타이젠 2.3에서 지원하고 있는 API 레퍼런스를 살펴보면,

API Reference > Native Application > Native API Reference > Base > sqlite

위와 같이 SQLite 항목을 찾을 수 있습니다.



SQLite 항목을 살펴보면,

SQLite는 가벼운 sql 데이터베이스로 라이브러리 형태로 제공된다고 언급되어 있습니다.

플랫폼에 탑재된 버전은 3.7.13(2015. 5. 28 현재 3. 8. 10. 2가 최신버전)이고,

Documentation은 SQLite 공식사이트(http://www.sqlite.org/docs.html)에 방문하여 보아야 합니다.

SQLite는 시중에 좋은 책도 많이 나와있고,

구글링으로도 깊이 있는 정보를 찾을 수 있기 때문에 API 사용법에 대한 언급은 하지 않겠습니다.


타이젠 플랫폼 소스를 살펴보면 몇가지 흥미로운 사실을 발견할 수 있습니다.


- sqlite3_open()을 직접 호출해주는 라이브러리가 몇개 있습니다.

  libslp-memo, libslp-alaram는 라이브러리 이름으로 파악해보건대,

  위의 라이브러리를 사용하는 앱에서만 DB 정보를 독점하는 것으로 보입니다.

  다른 프로세스에 앱 DB에 대한 정보를 건네줄 필요가 없기 때문에,

  앱에서 사용하는 라이브러리내에 직접 sqlite3를 사용했겠지요.


- 앱단에서는 sqlite3_open() 대신 db_util_open()이라는 유틸리티 함수를 사용하고 있네요.

  db_util_open() 함수 내부에서 sqlite3_open()을 하고, busy handler와 journal mode를 설정하고 있습니다.


/**
 * @brief invoke sqlite3_open with platform common configuration
 * @details register busy handler, create localized collation
 * @param [in] database file name (UTF-8)
 * @param [out] SQLite database handle
 * @param [in] option value
 * @return sqlite3 function return value will be returned
 * @see db_util_open_with_options()
 * @see db_util_close()
 *
 */
EXPORT_API int db_util_open(const char *pszFilePath, sqlite3 **ppDB,
                        int nOption);

위의 함수는 "git://review.tizen.org/framework/appfw/libslp-db-util"의 "include/util-func.h"에서 볼 수 있습니다.

첫번째 인자로 DB 파일이름을 넣어주면,

두번째 인자로 sqlite handle을 넘겨줍니다.


static int __db_util_open(sqlite3 *ppDB)
{
   /* ...생략... */
    /* Register Busy handler */
    rc = sqlite3_busy_handler(ppDB, __db_util_busyhandler, NULL);
    if (SQLITE_OK != rc) {
        DB_UTIL_TRACE_WARNING("Fail to register busy handler\n");
        sqlite3_close(ppDB);
        return rc;
    }

    /* Code to change default journal mode of sqlite3 is enabled so this option is disabled */
    /* Enable persist journal mode */
    rc = sqlite3_exec(ppDB, "PRAGMA journal_mode = PERSIST",
            NULL, NULL, &pszErrorMsg);
    if (SQLITE_OK != rc) {
        DB_UTIL_TRACE_WARNING("Fail to change journal mode: %d, %d, %s, %s\n",
                                sqlite3_errcode(ppDB),
                                sqlite3_extended_errcode(ppDB),
                                pszErrorMsg,
                                sqlite3_errmsg(ppDB));
        sqlite3_free(pszErrorMsg);
        sqlite3_close(ppDB);
   /* ...생략... */ }


위의 코드에서 busy handler를 등록하여,

DB가 EXCLUSIVE 등의 이유로 busy인 경우 retry하도록 설정합니다.


그리고 저널모드로 Persist를 지정해줍니다.

Persist는 롤백 루틴에 사용하는 파일을 일정한 크기로 유지하여,

파일 create & destroy에 따른 비용을 줄이고,

read & write를 최소한의 비용으로 할 수 있게,

디스크 공간은 버리고 성능을 택하는 저널방법입니다.

적어도 이 API만 보면, 타이젠은 성능 최우선의 정책을 취하는 것으로 보입니다.


busy handler에서는 usleep()을 직접 썼습니다.

usleep()은 프로세스 자체를 멈춰버리므로,

busy 상황에서 db_util_open()을 사용하여 강제로 앱프로세스를 멈추게 합니다.


static int __db_util_busyhandler(void *pData, int count)
{
    if(5 - count > 0) {
        DB_UTIL_TRACE_DEBUG("Busy Handler Called! : PID(%d) / CNT(%d)\n", getpid(), count+1);
        usleep((count+1)*100000);
        return 1;
    } else {
        DB_UTIL_TRACE_DEBUG("Busy Handler will be returned SQLITE_BUSY error : PID(%d) \n", getpid());
        return 0;
    }
}


db_util_open()은 framework이나 app단 모두에서 고루 사용하고 있습니다.

framework/api/favorites

framework/pim/calendar-service

framework/pim/contact-service

framework/messaging/email-service

framework/appfw/ail

framework/appfw/slp-pkgmgr

framework/appfw/alarm-manager

apps/home/menu-screen

apps/home/notification

apps/web/browser

등 상당수의 framework와 app에서 사용하고 있습니다.


DB를 open하는 방식으로도 타이젠 플랫폼의 정책을 엿볼 수 있습니다.

플랫폼 버전이 업그레이드될 때마다,

세부구현방식은 바뀔 수 있지만,

플랫폼 정책은 바꾸기 힘들겠죠.

경량화와 빠른 속도를 특장점으로 내세우는 타이젠은,

어쩌면, ivi나 IoT에 적합한 플랫폼일지도 모른다는 생각이 듭니다.


이제 너무 졸려 더 이상 단어를 쓸 여력이 없는 새벽 1시 54분에 마칩니다.


끝_




어느 플랫폼에서건 앱을 작성하게 되면,

앱의 라이프사이클에 대해 고민을 하게 됩니다.


앱 프로세스가 생성(fork & exec)된 직후에 수행해야하는 루틴,

종료 직전에 처리해야하는 루틴,

앱이 일시정지할 때 혹은 다시 시작할 때 필요한 루틴 등은,

앱 라이프 사이클을 구상할때 필히 고려해야하는 요소입니다.


안녕하세요, Tizen 개발자 윤진입니다.


타이젠 2.3 버전 기준으로 앱  라이프사이클은 "application"이라는 framework에서 관리합니다.

"application"의 소스를 살펴보려면,

아래처럼 command를 입력하여 소스를 clone 받을 수 있습니다.

간단하게 웹으로 보고 싶다면 여기를 누르면 됩니다.



앱은 main()에서 앱 라이프사이클을 관리해주는 ui_app_main()을 불러줘야 합니다.

ui_app_main()은 아래와 같이 선언되어있습니다.


/**
 * @brief Runs the application's main loop until ui_app_exit() is called.
 *
 * @details This function is the main entry point of the Tizen application.
 *          The app_create_cb() callback function is called to initialize the application before the main loop of application starts up.
 *          After the app_create_cb() callback function returns true, the main loop starts up and the app_control_cb() callback function is subsequently called.
 *          If the app_create_cb() callback function returns false, the main loop doesn't start up and app_terminate_cb() callback function is called.
 *          This main loop supports event handling for the Ecore Main Loop.
 *
 * @since_tizen 2.3
 * @param[in] argc The argument count
 * @param[in] argv The argument vector
 * @param[in] callback The set of callback functions to handle application lifecycle events
 * @param[in] user_data The user data to be passed to the callback functions
 *
 * @return 0 on success, otherwise a negative error value
 * @retval #APP_ERROR_NONE Successful
 * @retval #APP_ERROR_INVALID_PARAMETER Invalid parameter
 * @retval #APP_ERROR_INVALID_CONTEXT The application is illegally launched, not launched by the launch system
 * @retval #APP_ERROR_ALREADY_RUNNING The main loop already starts
 *
 * @see app_create_cb()
 * @see app_terminate_cb()
 * @see app_pause_cb()
 * @see app_resume_cb()
 * @see app_control_cb()
 * @see ui_app_exit()
 * @see #ui_app_lifecycle_callback_s
 */
int ui_app_main(int argc, char **argv, ui_app_lifecycle_callback_s *callback, void *user_data);

ui_app_main()에 들어가는 세 번째 인자 ui_app_lifecycle_callback_s는 라이프사이클을 위한 콜백을 등록해주는 부분입니다.

ui_app_lifecycle_callback_s는 아래와 같은 구조체입니다.


typedef struct
{
    app_create_cb create; /**< This callback function is called at the start of the application. */
    app_terminate_cb terminate; /**< This callback function is called once after the main loop of the application exits. */
    app_pause_cb pause; /**< This callback function is called each time the application is completely obscured by another application and becomes invisible to the user. */
    app_resume_cb resume; /**< This callback function is called each time the application becomes visible to the user. */
    app_control_cb app_control; /**< This callback function is called when another application sends the launch request to the application. */
} ui_app_lifecycle_callback_s;


위의 구조체를 하나 만들어준 후,

create와 terminate,

pause와 resume,

그리고 app_control 콜백을 만들어 구조체에 채워넣어주어야 합니다.


typedef bool (*app_create_cb) (void *user_data);
typedef void (*app_terminate_cb) (void *user_data);
typedef void (*app_pause_cb) (void *user_data);
typedef void (*app_resume_cb) (void *user_data);
typedef void (*app_control_cb) (app_control_h app_control, void *user_data);

app_create_cb은 앱이 fork-exec된 이후 mainloop에 진입하기 직전에 불립니다.

이후 앱이 terminate되어 메모리에서 제거된 후 다시 런칭되기 전까지 불리지 않습니다.

앱이 런칭된 직후 프로세스를 초기화하는 루틴은 이곳에서 수행됩니다.


static int __before_loop(struct ui_priv *ui, int *argc, char ***argv)
{
    int r;

    if (argc == NULL || argv == NULL) {
        _ERR("argc/argv is NULL");
        errno = EINVAL;
        return -1;
    }

    g_type_init();
    elm_init(*argc, *argv);

    r = appcore_init(ui->name, &efl_ops, *argc, *argv);
    _retv_if(r == -1, -1);

    LOG(LOG_DEBUG, "LAUNCH", "[%s:Platform:appcore_init:done]", ui->name);
    if (ui->ops && ui->ops->create) {
        r = ui->ops->create(ui->ops->data);
        if (r == -1) {
            _ERR("create() return error");
            appcore_exit();
            errno = ECANCELED;
            return -1; 
        }
        LOG(LOG_DEBUG, "LAUNCH", "[%s:Application:create:done]",
            ui->name);
    }   
    ui->state = AS_CREATED;

    __add_climsg_cb(ui);

    return 0;
}


위의 코드는 __before_loop() 함수에서 사용자가 등록한 create()를 불러주는 부분입니다.

(git://review.tizen.org/framework/appfw/app-core)

함수명에서 알 수 있듯,

mainloop에 진입하기 전에 create()을 불러주는 것을 확인할 수 있습니다.

create()를 실행한 직후 바로 mainloop에 진입합니다.

mainloop에서는 앱이 create()에서 수행한 루틴을 해석하여 첫번째 프레임을 렌더링하겠죠.


app_terminate_cb은 app_create_cb의 반대개념으로,

앱이 종료될 때 한 번 불립니다.

이 콜백에서 메모리를 정리하고,

현 프로세스에서 설정한 값들이나 사용자 데이터를 저장합니다.


app_pause_cb은 앱이 더 이상 화면에서 보이지 않게 된 순간 호출됩니다.

pause에서는 앱이 점유하고 있던 메모리를 내려놓거나,

pause되기 전까지 사용자가 수행한 결과물을 정리할 수 있습니다.


app_resume_cb은 화면에서 사라진 앱이 다시 화면에 보이게 되면 불립니다.

홈/메뉴를 통해 앱 A를 런칭했다가,

홈키나 파워키로 앱 B가 앱 A 위로 올라간 후,

다시 앱 A에 진입하게 되면 앱 A의 resume 콜백이 불리게 됩니다.

resume에서는 앱이 pause때 일시정지해두었던 기능을 다시 수행할 수 있도록 설정하면 됩니다.


app_control_cb은 위에서 설명한 create/terminate/pause/resume과는 달리 app_control_h을 인자로 받습니다.

app_control_cb은 create나 resume와는 다르게,

앱을 런칭하는 쪽에서 특정 정보를 app_control_h에 채워서 줄 수 있습니다.

때때로 앱은 런칭상황에 따라 다른 뷰를 보여주거나 다른 동작을 해야할 필요가 있겠죠.

예를 들어, 홈/메뉴를 통해 앱을 실행할 때와 스토어에서 앱을 실행할 때 서로 다른 뷰를 보여줄 필요가 있을 수도 있습니다.

이때 홈/메뉴는 홈/메뉴에서 앱을 런칭한다는 정보를 실어보내고,

스토어는 스토어에서 앱을 런칭한다는 정보를 실어보낸다면,

앱은 각각의 상황에 맞춰 뷰를 구성할 수 있습니다.


샘플앱을 런칭시켜 어떤 콜백이 불리는지 살펴보면 흥미로운 사실을 알 수 있습니다.

홈/메뉴를 통해 앱을 런칭시키면,

단지 app_create_cb만 불리지 않습니다.

총 3개의 콜백이 차례대로 불립니다.



app_create_cb은 애초에 설명한대로 앱을 런칭하여 mainloop에 진입하기 전에 불리게 됩니다.

하지만, app_control_cb은 왜 불리는 것일까요?

app_control은 런칭을 요청한 caller 앱에서 callee 앱으로 요청합니다.

callee 앱은 mainloop에 진입한 직후에 app_control_cb()을 처리하게 되죠.


static int __app_launch_local(bundle *b) 
{
    if (!aul_is_initialized())
        return AUL_R_ENOINIT;

    if (b == NULL) {
        _E("bundle for APP_START is NULL");
    }   
    if (g_idle_add(__app_start_internal, b) > 0)
        return AUL_R_OK;
    else
        return AUL_R_ERROR;
}

위의 코드는 caller 앱에서 g_idle_add()를 등록하여 callee 앱에서 등록한 app_control_cb을 불러주도록 설정하는 부분입니다.

저 코드로 앱이 idle 상태에 진입한 직후에 app_control_cb의 루틴을 타게 되는 것입니다.

(git://review.tizen.org/framework/appfw/aul-1)


app_control_cb 내에서 윈도우를 elm_win_activate()를 이용하여 최상단으로 올리면,

드디어 앱이 사용자에게 노출이 됩니다.


앱이 일부의 영역이라도 보이게 되면, app_resume_cb()이 불리게 됩니다.

resume()은 앱이 보이게 되면 불리는 콜백이기 때문에,

초기런칭루틴에서도 예외없이 불릴 수밖에 없습니다.

따라서 앱 런칭 시점에는 세 개의 콜백이 약간의 시간차를 두고 연달아 불리게 되죠.


Application Life Cycle Management


이상 타이젠의 앱 라이프사이클을 간단하게 설명하였습니다.

앱 라이프사이클을 잘 이용하면,

런칭속도 개선에 힘을 보탤 수 있습니다.


오늘은 여기서 마무리할까 합니다.

30분이면 포스팅할 수 있을거란 애초의 기대에서 벗어나

3시간째 글을 쓰고 있네요...

아무래도 내일 제 시간이 일어날 수 있을지 걱정이 됩니다.


끝_

혼자 개발하지 않는 이상,

팀원과 개발 중인 소스를 시시각각 공유해야할 필요가 있습니다.

타인과 데이터를 공유하는 수많은 방법이 있겠지만,

2015년 현재 가장 대중적으로 이용되는 git을 이용하여 소스를 공유하고자 합니다.


안녕하세요, Tizen 개발자 윤진입니다.


Tizen SDK 내에 git repository를 clone하거나 pull 받을 수 있는 기능은 없습니다.

Tizen SDK와 git은 서로 별개의 독립적인 모듈입니다.


따라서,

Tizen SDK에서 작업한 후,

git에서 수정사항을 push 하는 과정을 거쳐줘야 합니다.

vim에서 에디팅을 하고,

커맨드라인에서 git 명령어로 push 하는 것과 마찬가지의 절차입니다.


* 준비물

1. GIT Repository

2. Tizen SDK Project 

   (http://storycompiler.tistory.com/20 샘플앱 생성 참고)


위의 준비물은 미리 준비되어 있다고 가정하겠습니다.

Tizen SDK를 실행하여 git으로 공유하고자 하는 프로젝트를 엽니다.

그리고 File > Export 메뉴를 선택합니다.



Export 팝업창이 아래와 같이 뜹니다.

General > File System 메뉴를 선택합니다.



Next 버튼을 누르면,

SDK에 등록된 모든 프로젝트 리스트가 왼편에 나옵니다.

git repo.로 옮기고자하는 프로젝트를 선택합니다.

오른편 박스에는 해당 프로젝트에서 Export 하고자 하는 메타파일들이 나옵니다.

해당 파일들은 프로젝트를 위한 메타파일들이므로 모두 선택해야합니다.



To directory는 옮기고자 하는 git repo.의 루트 디렉토리 혹은 부모 디렉토리를 선택합니다.

- 루트 디렉토리 : git repo. 밑에 프로젝트명으로 디렉토리가 생성됩니다.

  git repo.에 다수의 프로젝트를 관리하고자 할때 효과적입니다.

  프로젝트 디렉토리 밑에 inc, src, res 와 같은 각종 디렉토리가 생성됩니다.

- 부모 디렉토리 : 프로젝트명과 git repo.명이 동일하게 설정된 경우,

  부모 디렉토리를 선택하면 git repo. 루트디렉토리에 바로 inc, src, res와 같은 디렉토리가 생성됩니다.

  한 git repo.에 하나의 프로젝트를 관리하고자 할때 사용하면 됩니다.


git repo.의 루트 디렉토리를 선택하여,

루트 디렉토리 밑에 프로젝트 디렉토리를 생성하였지만,

프로젝트 디렉토리만 없애고 루트디렉토리를 사용하길 원할 수도 있겠죠?

그럴 경우 프로젝트 디렉토리 안에 있는 모든 파일(숨김 파일 포함)을 루트 디렉토리로 복사하세요.


여기까지 진행하였다면,

git add / commit / push를 하세요.

팀원과 소스를 공유하기 위한 모든 준비가 끝났습니다.


팀원은 위에서 push한 커밋을 pull 받은 후,

Tizen SDK에서 File > Import 메뉴를 선택합니다.



Import 타입을 선택할 수 있는 팝업메뉴가 나타납니다.

General > Existing Projects into Workspace

위의 메뉴에 진입합니다.



Next 버튼을 눌러 다음으로 진행합니다.

Select root directory 라디오 버튼을 선택합니다.

그리고, Import 하고자 하는 git repo.의 루트 디렉토리를 기입하세요.



Finish 버튼을 누르면,

SDK 좌측에 좀 전에 Import한 프로젝트가 있는 것을 확인할 수 있습니다.


이제 SDK에서 코드를 마음껏 수정하고,

git 명령어를 통해 수정사항을 관리하면 됩니다.


팀원들과 합심하여 앱개발을 시작해보세요.


끝_

애초에 타이젠 앱을 짜려고 마음을 먹은 것은,

5월 5일 어린이날,

초딩러시를 피하기 위해 두문불출할 때였습니다.

(http://storycompiler.tistory.com/14)


그날 이후,

SDK를 설치하고(http://storycompiler.tistory.com/15),

타이젠 오픈소스를 다운로드 받고(http://storycompiler.tistory.com/16),

Z1을 구해 개발자+시료인증(http://storycompiler.tistory.com/20)까지 하니,

5월 25일 부처님 오신 날이 되었네요.


20일이 지나는 동안,

본격적인 코딩은 커녕 앱 아키텍쳐를 고민해보지도 않았다.

갈 길이 멀어요.


안녕하세요, Tizen 개발자 윤진입니다.


그렇다고 오늘 아키텍쳐를 고민하거나 코딩을 하겠다는 것은 아닙니다.

오늘은 템플릿 소스를 분석하여 타이젠 앱 디렉토리 구조를 살펴보겠습니다.


SDK를 런칭하여 템플릿 프로젝트를 만들어보죠.

File > New > Tizen Native Project

위의 메뉴로 진입하여 Native Project를 생성해보겠습니다.



Template > MOBILE-2.3 > UI Application > Basic UI Application (EDC)

차례대로 선택하여 기본앱을 위한 템플릿을 선택합니다.

하단의 Project name은 원하는 이름으로 지정합니다.

Package name은 Project name에 "org.tizen." 접두사가 붙습니다.

하지만, 원하는 대로 수정할 수 있습니다.

Finish를 눌러 프로젝트를 생성해보세요.



SDK 상에는 위의 디렉토리와 파일이 자동으로 생성됩니다.

앱의 root 디렉토리에 5개의 서브디렉토리와 1개의 메타파일이 생겼습니다.

위에 누락된 data 디렉토리를 추가하여 간단하게 설명하면 아래와 같습니다.



폴더명은 축약어와 풀네임을 혼용하였습니다.

inc, res, src, lib은 축약어로,

data와 shared는 풀네임으로 디렉토리 이름을 정했습니다.

최근 네이밍 트랜드로는 풀네임이 대세이지만,

inc, res, src, lib은 축약어로 오랜 기간 사랑을 받았기 때문에,

(오랜 고민과 토의 끝에) 축약어와 풀네임을 혼용하기로 결정된 것으로 보이네요.


- inc에는 앱 내부에서 정의하고 사용할 함수의 선언이 담긴 헤더파일을 넣습니다.

  대규모 프로젝트인 경우 inc 내에 디렉토리가 추가될 수 있습니다.

  디렉토리 내에 헤더파일이 각각의 의미에 맞게 배치되겠죠.


- src에는 SDK를 통하여 빌드될 소스파일들이 채워집니다.

  소스 디렉토리 내부에도 트리구조의 디렉토리를 추가할 수 있습니다.

  소스파일은 각각의 의미에 맞게 디렉토리에 배치합니다.


- res에는 앱 내에서만 사용할 리소스파일, edc 혹은 이미지 파일들을 넣습니다.

  edc 파일은 타이젠의 네이티브 UIFW인 EFL에서 사용하는 레이아웃을 위한 소스파일입니다.

  edc 파일은 빌드타입에 파싱되어 이미지파일까지 머금은 edj 파일로 변환이 됩니다.

  edj 파일은 c 파일 내부에서 edje_object prefix의 함수군으로 통제할 수 있습니다.

  따라서 기본적으로 생성되는 디렉토리 이름이 res/edje 입니다.


- lib은 빌드가 완료된 라이브러리 파일 자체가 놓이는 곳입니다.

  SDK로 빌드할 수 없어 외부에서 빌드했거나,

  소스없이 라이브러리만 사용할 수 있는 경우 lib 디렉토리에 라이브러리 파일을 위치시키죠.


- data는 앱내에서 읽고 쓰기가 가능한 xml이나 db파일을 저장하는 곳입니다.

  앱에서 사용하는 데이터 파일들을 초기화하기 위한 목적으로 사용합니다.


- shared는 아이콘처럼 다른 앱에서 접근할 수 있는 공간입니다.

  타이젠의 앱디렉토리 정책상 다른 앱의 디렉토리에는 앱권한의 프로세스가 접근할 수 없습니다.

  다만 역시 앱권한의 홈/메뉴/작업관리자 등의 앱에서 앱아이콘을 보여주어야 하기 때문에,

  앱 내의 shared 디렉토리에 위치한 아이콘에는 어느 앱이나 접근할 수 있게 허용하였습니다.


앱 소스 디렉토리 구조에는 군더더기가 없습니다.

SDK로 빌드를 진행하면,

패키지도 별도의 디렉토리 구조를 갖게 됩니다.

패키지 디렉토리 구조도 차후에 다시 설명할 기회가 있을 것입니다.


2015년에도 '월'요일에 공휴일을 만들어주신 부처님의 한 수에 감사드리며 포스팅을 끝마칩니다.


끝_


* References

https://developer.tizen.org/dev-guide/2.2.1/org.tizen.native.appprogramming/html/app_dev_process/project_files_and_folders.htm


+ Recent posts