2015/05/31

Fedora 22 설치 소감


이전 글에서 리눅스 배포판 들은 도찐개찐이라고 했지만, 지난 주에 Fedora 22가 출시되었다길래 Virtual Box에 설치해 보았다. 우분투를 주로 쓰다보니 페도라를 언제 썼었는지 기억이 가물가물하다. 중간에 CentOS는 좀 사용했었다. 페도라가 리눅스 배포판 중 bleeding-edge인지는 잘 모르겠지만 최신 S/W 들을 빨리 채택하는 것은 틀림없다. Redhat이 페도라에서 검증된 것들을 수용한다는 점과 리눅스의 새로운 기능(기술?)들을 페도라에서 빨리 접해 볼수 있다는 점은 리눅스 사용자들에게 고무적인 일이다.

Fedora 22가 땡겼던 이유...

Gnome Shell 3.16이 뭐가 달라졌나 궁금했고, kernel 4.0에서 live patching이 잘 동작하는지 보고 싶었다. 그리고, Wayland가 탑재되어 있어서 한번 써보고 싶었다. 부가적으로 가상 Desktop 환경인 Vagrant 도 궁금했고, 패키지 관리자로써 Yum을 버리고 DNF를 본격적으로 채택한 점도 눈에 띄었다. 페도라 업그레이드에 대한 세부 사항은 여기를 참고하면 된다.


설치시 문제

그런데, Wayland나 Vagrant는 VirtualBox에서는 테스트 해 볼 수가 없다. 그래서 iMac에 설치해 보려고, 하드디스크의 Fedora 설치 iso를 가지고 부팅했는데 우분투와 마찬가지로 WIFI가 당장 동작하지 않았다. 당장 유선랜을 쓸 수 있는 환경이 아니어서 iMac에 설치하는 것은 보류할 수 밖에 없었다.

설치시에 페도라가 우분투보다 안좋은 점은 non-free 드라이버가 설치 iso에 아예 들어있지 않다는 것이었다. 더구나, 설치 파일들이 통째로 sqashfs 이미지 파일에 들어 있는데 알고 보니 패키지로 구성되어 있는게 아니었다. 걍 페도라가 설치된 채로 sqashfs에 들어 있었다. 설치 iso로 설치한다 뿐인지 실제로는 sqashfs 이미지를 풀어서 하드디스크에 복사해 넣은 다음 필요한 설정만 설치 패키지에서 잡아 주는 형태다.

설상가상으로 페도라는 네트워크이 안되면 설치하지 말라고 얘기할 수 밖에 없다. Broadcom WIFI 드라이버를 일단 VirtualBox의 페도라에서 내려 받아 보려고 했는데 Fedora 22 버전은 아직 올라와 있지도 않았다. Fedora 21 버전에 대한 non-free 드라이버를 사용해도 될 것 같긴 한데 이런 방법은 패키지 의존성 문제가 생길 수 있으니 사용하지 말란다. 소스를 컴파일 하는 방법도 생각해 봤는데 기본적으로 gcc 패키지도 설치되어 있지 않기 때문에 네트워크가 안되면 별로 해볼 수 있는 방법이 없다. 뭐, VirtualBox에서 컴파일해서 iMac에 복사하는 방법도 있지만 귀차니즘이 밀려와서 관두기로 했다.

하드디스크의 Fedora 설치 iso로 부팅하기 위한 Grub 메뉴

나중에 iMac에 페도라를 설치할 수도 있기 때문에 헤맸던 것을 정리해 둔다. 우분투의 Grub에 아래와 같이 메뉴 엔트리를 추가하면 우분투 파티션에 있는 페도라 설치 iso를 이용해서 부팅할 수 있다. 아래 내용을 /etc/grub.d/40_custom 파일에 추가한 후,

$ sudo nano /etc/grub.d/40_custom

menuentry "HDD Fedora 64-bit iso" {
    set isoname="Fedora-Live-Workstation-x86_64-22-3"
    set isolabel="Fedora-Live-WS-x86_64-22-3"
    set isofile="/boot-isos/${isoname}.iso"
    loopback loop (hd0,9)/$isofile
    linux (loop)/isolinux/vmlinuz0 iso-scan/filename=${isofile} root=live:CDLABEL=${isolabel} rootfstype=auto ro rd.live.image quiet rhgb rd.luks=0 rd.md=0 rd.dm=0
    initrd (loop)/isolinux/initrd0.img
}

$ sudo update-grub

위의 메뉴엔트리에서 주의해야 할 부분이 isoname과 isolabel이 다르다는 점이다. 이것 땜에 좀 많이 헤매야 했다.

설치 과정

페도라 설치 UI도 직관적이어서 우분투와 마찬가지로 설치시에 큰 어려움이 없다. 한글로도 설치가 잘된다. 한가지 우분투와 다른 점은 사용자 계정은 물론이고 root 계정도 설정해야 한다는 것이다. 우분투에 적응이 되어 있어서 root 계정을 사용하는 것은 부담스럽다. 패스워드 두개를 외워야 한다는 거... 그렇다고 패스워드를 한개로 통일하면 root 계정을 쓸 이유가 없다.

페도라 설치시 파티션을 자동 설정으로 두면 /boot 파티션을 ext4 파일시스템으로 만들고, 나머지 공간은 LVM으로 구성한다. 서버 사용자라면 문제될 게 없지만 Desktop 환경에서는 Logical Volume을 사용하는 것보다 물리적인 파티션을 사용하는 것이 안전하다는 고정관념이 머리 속에 남아 있다.

설치 후 최초 부팅했을 때 사용자 환경 설정을 한다는 점도 다른 점이다. 여기서 한글(Hangul) 입력기를 선택하면 ibus-hangul을 바로 사용할 수 있다. 초보자들에게는 이런 방법이 더 나을 수도 있겠다 싶다.

root 계정 안쓰기

사용자 계정이 하나 있으니까 우분투 처럼 root 계정을 안쓰고 sudo 권한을 주기로 했다. sudo 권한을 주려면 당연히 root 계정을 사용해야 한다.

$ su - root

우선 아래와 같이 사용자 계정에 wheel 그룹을 추가해 준다. Redhat 계열 리눅스에서 wheel 그룹은 /etc/sudoers 파일에 root 권한을 갖도록 설정되어 있다.

$ usermod -aG wheel aaa

이제 root 암호에 lock을 걸어 버림으로써 root 계정으로 직접 또는 ssh 등으로 login할 수 없게 된다.

$ passwd -l root

나중에 root 계정을 다시 사용하고자 한다면 사용자 계정에서 아래와 같이 해주면 된다.

$ sudo passwd -u root

사용자 계정에서 sudo를 사용하기 위해서는 사용자 계정으로 재로그인 해주면 된다.


DNF 패키지 관리자

DNF는 Dandified Yum(멋쟁이 yum?)이란다. 좀 기억하기는 어렵다. yum을 버리는 이유는 패키지 의존성 문제를 해결하기 어렵고 yum에 대해 문서화가 잘 안되어 있어서 API가 혼란스럽기 때문이란다. 사용법 자체는 yum과 거의 비슷하기 때문에 큰 문제는 없으나(?)... 그래도 배우는데 시간이 걸릴 듯...

일단, WIFI 문제 때문에 repo를 추가해야 했는데 DNF 사용법을 몰라서 한참 헤맸다. yum과 좀 많이 다르다... 아래와 같이 추가하면, 결국은 /etc/yum.repos.d에 아래의 free와 nonfree repo 두 개가 추가된다.

$ sudo dnf config-manager --nogpgcheck --add-repo http://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
$ sudo dnf config-manager --nogpgcheck --add-repo http://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm

그런데, 아직 Fedora 22 드라이버가 올라와 있지 않아서 dnf로 kmod-wl 패키지를 다운로드할 수 없었다.

패키지 의존성 문제를 해결했을지는 모르지만, 패키지 자체의 문제가 있어 보인다. 위의 repo가 아직 Update 되지 않은 것은 제외하고도... gcc를 설치하는데 커널 소스를 debug 버전으로 설치하더라. VirtualBox guest addition을 컴파일하는데 오류가 나서 알고 봤더니 kernel debug 버전 때문에 생긴 문제였다는... dnf로 이거 지우고  kernel-devel을 다시 설치했더니 잘 된다.

또, 한가지 문제는 dnf는 command-line이고 이와는 별개로 패키지 설치를 위해 Gnome Shell에서 우분투 소프트웨어 센터와 비슷한 Software란 놈을 사용할 수 있는데 패키지 설치 history가 통합 관리되지 않는다는 점이다. dnf로 설치한 history만 알 수 있다. 우분투는 apt-get으로 설치하든 우분투 소프트웨어 센터를 사용하든 설치된 패키지에 대한 history를 우분투 소프트웨어에서 확인할 수 있다. 물론, dpkg나 deb으로 설치한 소프트웨어 history는 우분투에서도 안보이지만 이 들이 표준 방식은 아니니까...

이 Software란 놈이 또 한가지 웃기는 점이 있는데, 페도라 패키지를 Update하면 설치하기 전에 페도라를 한번 부팅하고나서 Update 설치 후에 또 한번 페도라를 부팅시킨다. 아니, kernel 4.0에서는 심지어 kernel update조차 컴퓨터를 부팅시키지 않으려고 하는데 이 무슨 망나니짓임? 한번도 아니고 두번씩이나 컴퓨터를 부팅시키다니... 아, 물론 dnf를 사용하면 아예 부팅하지 않는다.

$ sudo dnf upgrade

물론, 리눅스에서 부팅하지 않는다고 방금 update한 패키지를 바로 사용할 수 있다는 것은 아니다. 하지만 대부분의 경우에는 재로그인 하는 것만으로도 update한 패키지를 바로 사용할 수 있다.

Gnome 3.16

Gnome 3.16에서 눈에 띄는 두 가지 달라진 점이 있었다. 하나는 Notification이 달력과 통합되어 Top bar의 중앙에 날짜 옆에 점으로 표시된다는 것이다. 또, 한가지는 Window Title Bar가 매우 단순해 졌다는 것이다. 가령, gedit를 예로 들면, 수직으로 Window Title Bar, Menu Bar, Tool Bar의 세줄이 필요했던 것을 한 줄에 몰아 넣었다. 구글 chrome과 비슷하다. 세 줄이 한 줄로 줄었으니 화면의 수직 공간을 매우 절약하게 된다. 물론, Gnome-shell에서 딸려오는 애플리케이션에 한해서만 그렇다는 얘기다.

사실, 내가 우분투의 Unity Desktop 환경을 고집해 온 가장 큰 이유 중의 하나가 수직 공간에서 소위 LIM(Locally Integrated Menu)이 Window Title Bar와 통합되어서 2줄이 1줄이 되기 때문이었는데 Gnome 3.16에서는 무려 3줄이 1줄로 된다는 것이 매우 기쁜 일이 아닐 수 없다. 다만, Menu와 Tool Bar가 통합됨으로 인해 애플리케이션에서 특정 기능을 사용하고자 할 때는 불편함이 생긴다.

Unity Desktop의 Top panel을 숨길 수 없는데 반해서 Gnome Shell은 Hide-Topbar shell extension을 설치하면 Top Bar autohide가 되기 때문에 수직 공간을 추가로 절약할 수 있다. 이것은 엣 버전에서도 가능했던 일이다.

그러나, 아쉬운 점은 통합된 Window Title Bar의 수직 폭이 좀 넓어 졌다는 것이다. ~/.config/gtk-3.0/gtk.css에서 줄일 수 있다는데 안 줄어 들더라... 덩달아서 gedit의 경우 파일을 여러개 열면 Tab이 생기는데 Tab의 수직 폭도 넓어 졌다는... 쩝... 그래서, 결과적으로 Unity Desktop 환경과 비교하면 뭐 별로 수직 공간이 절약된 느낌이 들지는 않는다. 도대체 어쩌자는 거임...? ㅠ.ㅠ

Kernel 4.0 live patching

아직 페도라 업데이트 중에 커널이 포함된 적이 없어서 아직 테스트를 해볼 수 없었다. 나중에 기회가 되면... Fedora 22 출시된지 얼마 되지도 않았는데 무리한 요구를 할 수는...

Fedora 22에 대한 전반적인 소감

우분투 사용자들에게 페도라를 권하고 싶지는 않다. 특히, 리눅스 초보자들에게는 우분투를 권하고 싶다. 기본적인 패키지 관리 문제도 그렇고, H/W 드라이버 문제도 그렇고 우분투가 훨씬 사용자 친화적인 Desktop 환경이라고 볼 수 있다.

다만, 앞서 얘기했듯이 최신 Open Source 소프트웨어들을 빨리 접해 보고 싶은 이들에게는 당연히 우분투 보다 Fedora를 권하고 싶다.

fedora 22에서의 한글 입력기 설정은 우분투 14.10이후의 ibus-hangul 설정 방법과 근본적으로 동일하다고 보면 된다.

2015/05/30

리눅스 배포판 선택에 대한 잡담


리눅스 배포판이 문제?

리눅스 사용자들이 어떤 배포판을 선택할지 고민하는 경우가 많기 때문에 약간의 고민을 덜어 주고 싶다. 리눅스 관련 커뮤니티에 올라온 질문 들 중에 이런 내용들을 보면 좀 답답하게 느낄 때가 있어서이다. "우분투에서는 WIFI가 잘 되는데 페도라에서는 안된다." "페도라에서는 한글입력이 잘 되는데 우분투에서는 안된다." 또는, "Gnome Shell에서는 한글입력이 잘 되는데 KDE에서는 안된다." 더구나 그 결론이 잘 되는 배포판이나 Dektop으로 갈아타야겠다고 할 때가 많다는 것이다.

기본적으로 동일한 PC 환경에서 특정 배포판 또는 Desktop 환경에서는 잘 되는데 다른 배포판이나 Desktop 환경에서 안되는 이유는 패키지 설치 방법이나 설정 방법이 조금씩 다르기 때문이다. 그러니까, 단지 이런 류의 문제들 때문에 배포판이나 Desktop을 바꾸는 것은 시간 낭비라는 것을 말해 주고 싶다.

나의 경우에, 다른 배포판이나 Desktop을 설치하는 주된 이유는 새로운 기능을 빨리 접해 보고자 할 때이다. 물론, 이외에도 PC 사양, 시스템 안정성이나 편리성, 시스템 보안, 특별한 리눅스 사용 목적, Desktop에 대한 Design 등도 배포판이나 Desktop 선택에 대한 주요 이유가 된다.

리눅스 배포판 들...

우선, DistroWatch에서 배포판에 대한 Page 방문 통계 순위를 제공하는데, 어느 정도 신뢰할 수 있기 때문에 여기를 참고하면 괜찮은 배포판을 선택할 수 있다. Top 5까지는 국내 사용자들도 많기 때문에 문제가 생겼을 때 도움을 얻을 수 있다. 리눅스 배포판들은 역사적으로 특정 시점에 인기 있는 배포판을 기반으로 이를 개선해서 새로운 배포판이 탄생한 경우가 많기 때문에 족보를 갖고 있다고 볼 수 있다. 주요 배포판 중에 Debian이나 Redhat, OpenSuse, Slackware 등은 고조 할배 뻘이 된다. Ubuntu는 Debian에서 분기했는데 Mint나 elementary OS 등 자식들을 많이 두고 있다. Fedora는 CentOS와 함께 Redhat에서 갈라져 나왔다. 같은 할배 밑의 자손들은 리눅스 명령이 거의 비슷하고 대체로 binary 호환성도 유지된다. 할배가 다르면 리눅스 기본 명령은 대부분의 Unix 계열 명령과 거의 같지만, 명령 자체가 다른 경우가 많고 binary 호환성도 보장 받지 못한다. 또한, 기본 명령조차도 배포판에 따라 실행 결과가 조금씩 다를 수도 있다.

그리고, 할배가 낫냐 손자가 낫냐 따지는 것도 큰 의미는 없다. 할배를 개선해서 손자가 탄생했지만, 할배도 계속 Upgrade하면서 새로운 것들을 받아 들이기 때문이다. 더구나, 기본 패키지들은 할배의 저장소에서 가져 오는 경우가 많기 때문에 손자의 S/W 버전이 할배 보다 낮은 경우도 많다. 대부분의 리눅스 배포판들은 부모 배포판이 버전 업되고 나서야 자식 배포판이 버전 업된다.

리눅스 Desktop 환경 들...

리눅스 배포판 내에서도 Desktop(GUI) 환경에 따라서 또 사용자가 나뉜다. Desktop 환경에 따라서 리눅스에 대한 Look & Feel이 달라질 수 있고, GUI 애플리케이션도 조금씩 다르다. Ubuntu만 해도 기본 Desktop인 Unity외에 Gnome Shell, KDE(Kubuntu), XFCE(Xubuntu), LXDE(Lubuntu), Mate, Cinnamon, Panthon... 등등을 사용할 수 있다. Desktop 환경도 대부분의 경우에는 다른 리눅스 배포판에서도 사용할 수 있다. 예를 들어, Gnome Shell은 페도라의 기본 Desktop 환경이고, KDE나 Mate 등을 페도라에서도 사용할 수 있다. 더구나, 한 배포판 내에서 여러가지 Desktop 환경을 동시에 설치해서 사용할 수 있다.

다만, 특정 Desktop 환경에서 다른 Desktop 환경의 애플리케이션을 사용할 목적이라면 굳이 여러개의 Desktop을 설치할 필요도 없다. 해당 애플리케이션의 패키지를 그냥 설치해서 사용하면 된다. 가령, Gnome Shell에서 KDE 애플리케인션인 Okular만 설치해서 사용할 수 있다. 물론, Gnome은 GTK 라이브러리 기반이고 KDE는 Qt 라이브러리 기반이기 때문에 Okular를 설치하면 필요한 Qt 라이브러리 들이 같이 설치된다.

도찐개찐

하지만, 궁극적으로 내가 하고 싶은 얘기는 리눅스 배포판 들은 개콘 코너에서 얘기하듯이 도찐개찐이라는 것이다. 할배 배포판들까지 포함해서 하는 얘기다. 가령, 우분투에서 Gnome Shell을 쓰는 것과 페도라에서 Gnome Shell을 쓰는 것은 겉보기에 아무런 차이가 없다. GUI 애플리케이션들은 Desktop 환경이 동일하면 리눅스 배포판과 상관없이 거의 동일하다. 다만, 리눅스 배포판의 차이에 따른 명령어들은 다르다. 가장 차이가 많이 나는 부분은 Package 관련 명령들인데 이것을 빼면 대부분의 명령이나 애플리케이션 들은 거의 동일하다.

근본적으로 리눅스 kernel이나 H/W 드라이버 들은 똑같다. 특정 배포판에서 H/W가 동작하는데 다른 배포판에서 동작하지 않을 확률은 거의 0에 가깝다. 기본 S/W나 애플리케이션들도 Open Source이기 때문에 대부분의 경우 특정 배포판에서만 안되는 일은 거의 없다고 봐도 무방하다.

리눅스 배포판/Desktop 추천

뭐, 리눅스 커뮤니티에 기여하기 위해 일부러 리눅스 배포판이나 Desktop 환경을 바꾸는 사람들에게는 감사해야겠지만, 단지 어떤 것이 예뻐 보인다는 이유만으로 바꾸는 사람들에게는 맘에 드는 한가지를 선택해서 정착하라는 것이 결론이다.

가령, 우분투 자손인 elementary OS는 무겁지도 가볍지도 않은 중간 쯤의 Desktop 환경인 Pantheon Desktop 환경을 사용하는데 보기는 좋으나 여러가지 불편함을 감수해야 한다. 그래서, 일반 사용자에게 우분투는 가장 무난한 배포판이고 Desktop 환경은 개인적인 선호도가 있기 마련이니까 Unity, Gnome Shell, KDE 셋 중에 하나를 추천한다. Netbook과 같이 PC 사양이 너무 낮은 경우(Mobile CPU, RAM 2GB 미만)에 한해서 리눅스 GUI를 사용하고자 할 경우에는 어쩔수 없이 Mate, LXDE/LXQt, XFCE 등을 권한다.

리눅스 Desktop 환경의 성능은 CPU가 특별히 매우 낮은 사양이 아니라면 RAM 용량에 의존하는데, 그래봤자 가장 무거운 Unity와 가벼운 편인 Mate하고 비교하면 최대 500MB 정도 밖에 차이나지 않는다. RAM이 4GB이상이면 Virtualbox로 다른 guest OS를 설치해도 큰 문제는 없다.

2015/05/24

Ubuntu 15.04 systemd 부팅 시간 줄이기


systemd 매번 부팅시마다 fsck 검사하는 문제

우분투 15.04부터 본격적으로 systemd를 채택했으니 여기에 적응할 필요가 생겼다. 기본적으로 매번 부팅할 때마다 fsck가 파티션 별로 file system check를 하다보니 10초 정도를 허비하고 있다는 것을 알았기 때문이다. Ctrl-C로 취소할 수 있다고 메시지가 뜨지만 실제로 Ctrl-C도 동작하지 않는다. 이 두 가지 문제 중에서 첫번째만 해결되면 Ctrl-C가 동작하지 않아도 큰 문제는 없다.

systemd에서는 부팅 후 터미널에서 아래의 명령으로 부팅 시 소요 시간과 서비스(systemd unit service)별 병목 구간을 알 수 있다.

$ systemd-analyze
$ systemd-analyze blame
$ systemd-analyze critical-chain

예전에 fsck 주기를 mount 횟수나 일정 시간 간격으로 설정할 수 있었던 기억이나서 tune2fs를 가지고 별짓을 다 해 보아도 부팅시 fsck가 파일시스템 검사하는 것을 막을 방법이 없었다. 구글링으로 찾은 방법은 /etc/fstab 파일에서 <Pass> 파라미터를 0으로 바꿔주면 해당 파티션은 fsck 검사를 하지 않는다는 것이다. 모든 파티션에 대해 파라미터를 0으로 수정하고 재부팅하니, 과연 10초 정도 부팅속도가 빨라졌다. 뭐 이 방법도 나쁜 방법은 아니지만, fsck를 고생해서 쓰라고 우분투에 넣었는데 안쓰는 것도 좀 미안한 생각도 들고, 왠지 가끔씩은 돌려야 할 것 같아서 더 좋은 방법을 찾아 보기로 했다.

e2fsck time-stamp 문제

우분투 관련 사이트에는 systemd 사용역사가 짧아서인지 문제점을 지적한 글을 찾을 수 없다. 역시, systemd를 처음 채택한 Fedora 관련 사이트에서 이 두가지 문제에 대한 글들을 찾을 수 있었다. fsck 버그란 얘기도 있고 fsck time-stamp 때문이란 얘기도 있다. 실제로 아래의 명령으로 fsck time-stamp 문제가 발생하는 것을 확인할 수 있었다.

$ journalctl | grep -i fsck
May 23 17:20:10 localhost.localdomain systemd-fsck[278]: ROOTFS: Superblock last write time is in the future.
May 23 17:20:10 localhost.localdomain systemd-fsck[278]: (by less than a day, probably due to the hardware clock being incorrectly set). FIXED.
또, 아래 명령으로 리눅스 ext 파티션의 mount와 최종 fsck 시간을 확인할 수 있는데 UTC time으로 로그가 찍힌다.

$ sudo tune2fs -l /dev/sda8

가만히 생각해 보니, Windows와 Ubuntu의 시간을 맞추기 위해 /etc/default/rcS 파일에서 UTC=yes 설정을 UTC=no로 바꾸었다는 것을 깨닫게 됐다. 이 설정은 컴퓨터 내장 시계의 시간 기준이 UTC인지 local time인지를 부팅시에 알려 준다. 추정컨대, H/W clock이 local time으로 설정되어 있어서 fsck 검사시에 local time으로 최종 시간을 파일시스템에 저장했는데 다시 fsck 검사할 때는 이것이 UTC time이라고 생각하기 때문에 발생하는 문제인 듯 싶다. 즉, 우리나라 local time인 KST = GMT+09로 UTC 시간 보다 9시간이 빠르다. 참고로, 시간 설정에 대한 정보는 아래의 명령으로 확인할 수 있다.

$ timedatectl status
$ sudo hwclock --show

문제 해결?

아무튼 /etc/default/rcS 파일을 원래대로 UTC=yes로 저장하고 재부팅했더니 파일시스템 검사 시간이 줄어 들었다. 검사 시간이 줄어드는 정도로는 안되고 아예 검사를 하지 말아야 한다. journalctl 명령으로 확인해 보니, EFI System Partition(ESP)에 대해서만 fsck가 파일시스템 검사를 했음을 알 수 있었다. ESP는 vfat 파일시스템이므로 fsck 검사시간 정보 등을 저장할 수 없다. 예외적으로 이 놈에 대해서만 /etc/fstab의 <Pass> 파라미터를 0으로 설정하기로 하였다. 이 후로 몇번 재부팅해봤는데 더이상 fsck가 돌지 않아서 부팅시간이 10초 정도 빨라졌다.

vfat 빼고는 생기지도 않았을 문제를 해결했다니 느낌이 좀 이상하네...

systemd 부팅 시간 쬐금 더 줄이기

systemd에서는 서비스들을 unit 단위로 관리한다. unit 간에 의존성이 있을 수도 있다. 일단 아래의 명령으로 현재 서비스 unit과 상태를 확인할 수 있다. 상태는 enabled, static, disabled, masked의 4가지가 있을 수 있는데, static은 다른 unit과 의존 관계가 있다고 이해하면 될 듯하다. masked는 disable 시킨건 아니지만 의존 관계를 유지하면서 해당 unit만 disable 시키는 방법인 듯하다. 물론, 부팅 후 특정 서비스 unit을 stop하거나 start 할 수도 있다.

$ systemctl list-units --type service
$ systemctl list-unit-files --type service

그리고, systemd 서비스 unit을 부팅시에 동작하지 않도록 하거나, 다시 동작하도록 하려면 아래의 명령을 사용하면 된다.

$ sudo systemctl disable <unit 명>
$ sudo systemctl enable <unit 명>

앞서 systemd-analyze blame 명령에서 서비스 unit 별로 실행시간 profile을 알 수 있는데 시간이 오래 걸리는 놈들 중 불필요한 서비스 unit을 disable 시키면 된다. 아래 두 놈을 시범삼아 제거해 보았다. 참고로, fsck의 경우도 systemd-fsck@.service를 disable 시킬 수도 있지만 다른 unit와 의존관계가 있었고 아래 두 놈은 의존관계가 없어 보였다.

$ sudo systemctl disable ModemManager.service
$ sudo systemctl disable bluetooth.service

결론

위의 두 놈을 제거후 재부팅했더니 5초 정도 부팅시간이 빨라졌다. fsck까지 포함해서 15초를 절약하게 됐다. 그래서 총 부팅시간이 35초에서 20초 정도로 줄었다는 것이 결론이다. 뭔가 허무한 느낌... 더구나 애초에 /etc/default/rcS 파일을 건드리지 않았으면 fsck에 대한 10초도 빨라진게 아니라는... ㅠ.ㅠ

참고 사항

위에서 우분투 H/W 시간 기준을 UTC로 바꾸고 Windows로 부팅하면 Windows에서는 그 시간을 local time이라고 해석하기 때문에 시간이 UTC time으로 바뀌어 9시간 느린 시간이 표시될 수 있다. 특히, NTP time 서버와 동기화 되지 않을 때 이 문제가 생기는데 Windows Scheduler에서 NTP 서버 동기화 작업을 걸어 주거나, Windows에서도 H/W 시계의 시간 기준이 UTC라고 알려 주는 방법도 있단다. Mac OS X의 경우에는 Unix 계열이므로 리눅스와 마찬가지로 timezone으로 시간을 설정하므로 시간 불일치 문제는 발생하지 않는다.

2015/05/07

Syntax Highlighter 구 버전으로 회귀


구관이 명관이라는 말이 실감난다. 최신 버전의 Syntax Highlighter를 이 블로그에 적용했었는데 구 버전으로 회귀해야했다. 신 버전은 테마를 사용할 수 있어 깔끔하고, 원저자의 사이트 링크들만 블로그 Template에 복사하면 되었기에 구글 블로그에 적용하기가 매우 편리하다.

그런데, 어제 저녁에 갑자기 내 블로그에 접속이 안되는 일이 발생했다. www.blogger.com에는 접속이 되는데 내 블로그에는 접속이 안되는 황당한 상황이었는데 Firefox가 원인을 알려 주었다. 내 블로그에 접속하려면 Syntax Highlighter 사이트를 거쳐와야 했는데 거기에 접속이 안되니까 내 블로그에도 접속이 안되는 상황이 발생한 것이다. 해킹을 당한건지 서버가 망가진건지 알 수 없지만 지금도 해당 사이트로는 접속이 안된다.

내 발등에 떨어진 불을 끄기 위해 신버전의 Syntax Highlighter를 걷어내야 했다. 그리고 구버전을 설치했다. 구 버전에서도 링크를 병행해서 사용해야 하지만 syntaxhighlighter.googlecode.com 링크를 사용하기 때문에 상대적으로 안전하다. 문제는 구버전과 신버전 tag가 달라서 예전에 올린 글들을 일일이 수정해야 하는데 귀찮아서 모두 수정할수 있을지 장담하기 어렵다.

요즘 알게 모르게 Cloud 환경을 많이 사용하고 있는데 구글 같은 대형 사이트가 아니면 언제든지 유사한 일을 당할 수가 있다. 뭐, 구글같은 사이트도 정책이 바뀔 수도 있으니 아무튼 미래에도 Cloud 환경이 안전하다고 맹신하면 안될 것이다. 전에 iPhone을 애들이 갖고 놀다가 비밀번호를 바꿨는데 기억을 못하는 바람에 iOS부터 새로 설치해야 했던 적이 있는데 다행히 iCloud를 사용하고 있어서 중요한 데이터가 거의 완벽히 복구된 적이 있었다. 그런 면에서 Cloud 환경이 편리한 것도 사실이다. 하지만, Syntax Highlighter 문제와 같이 Cloud 환경의 위험성에 대해서도 경각심을 가져야 할 것이다.

구 버전의 Syntax Highlighter 설치 방법은 stackoverflow.com에 올라와 있는데 Backup 차원에서 이 글에 다시 남긴다.

1. 먼저, blogger template을 백업

2. http://syntaxhighlighter.googlecode.com/svn/trunk/Styles/SyntaxHighlighter.css의 파일 내용을 </b:skin> tag 앞에 복사

3. 아래 내용을 </head> tag 앞에 복사: 필요한 brush 파일만 선택
<script src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shCore.js' type='text/javascript'></script>
<script src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushCpp.js' type='text/javascript'></script>
<script src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushCSharp.js' type='text/javascript'></script>
<script src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushCss.js' type='text/javascript'></script>
<script src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushDelphi.js' type='text/javascript'></script>
<script src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushJava.js' type='text/javascript'></script>
<script src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushJScript.js' type='text/javascript'></script>
<script src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushPhp.js' type='text/javascript'></script>
<script src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushPython.js' type='text/javascript'></script>
<script src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushRuby.js' type='text/javascript'></script>
<script src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushSql.js' type='text/javascript'></script>
<script src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushVb.js' type='text/javascript'></script>
<script src='http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/shBrushXml.js' type='text/javascript'></script>
4. 아래 내용을 </body> tag 앞에 복사
<script language='javascript'>
dp.SyntaxHighlighter.BloggerMode();
dp.SyntaxHighlighter.HighlightAll('code');
</script>
5. blogger template 저장

6. 사용 방법은 아래와 같이 <pre name="code" class=cpp></pre> tag 사용
<pre name="code" class="cpp">
int x = 0, y = 10;
</pre> 
7. 사용 가능한 Brush class: cpp, csharp, css, delphi, java, js, php, python, ruby, sql, vb, xml

2015/05/06

Qt 5.4 fcitx immodule Build 및 한글 사용


이전 글에서 우분투 15.04 Unity Desktop 환경에서 한글 입력기로 fcitx를 추전했는데, 막상 Qt 5.4의 Qt creator에서는 한영키가 동작하지 않아 황당했다. 당연히, 이 Qt creator로 build한 Qt 5.4 애플리케이션에서도 한영키가 동작할리 없다. 이전 글에서 테스트한 Qt 앱은 Qt 5.3에서 build 했던것 같다. 한마디로 우분투 패키지로 다운 받은 fcitx는 최신 버전의 Qt 5.4를 지원하지 않는 것 같다. 뭔가 달라진 듯...

구글링하니까 마침 일본 블로그에 fcitx build하는 방법이 올라와 있어서 다시 build 한 후 Qt5 immodule를 복사해 넣었더니 모든 Qt 애플리케이션에서 한글입력이 잘 된다. 다만, 그 블로그 내용을 그대로 따라하니까 안되더라... 그래서 다시 정리한다.

Qt 5.4에서 fcitx Qt5 immodule build

$ sudo apt-get install git cmake

$ git clone https://github.com/fcitx/fcitx-qt5.git
$ cd fcitx-qt5
$ git checkout 0.1.3

build 하기 전에 Qt 5.4가 설치된 홈 폴더를 미리 알아 두어야 한다. 나의 PC에는 /opt/OpenSrc/Qt에 설치하였다.

$ cmake . -DCMAKE_PREFIX_PATH=/opt/OpenSrc/Qt/5.4/gcc_64
$ make

기존 fcitx 패키지로 설치된 Qt5 immodule 대체 설치

기존의 우분투 fcitx 패키지에 딸려온 Qt5 immodule을 대체해버리는 것이 가장 좋은 설치 방법이다. 안전을 위해 원래 파일은 org.libfcitxplatforminputcontextplugin.so로 백업했다.

$ sudo mv /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/org.libfcitxplatforminputcontextplugin.so

$ sudo cp ./src/libfcitxplatforminputcontextplugin.so /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/

또 한가지 대체해야 할 파일이 있다는 것을 나중에 알았다. Qt5 immodule이 사용하는 runtime library 파일 하나가 같이 build 되었다. 이 놈까지 버전이 일치해야 한다.

$ sudo mv /usr/lib/x86_64-linux-gnu/libfcitx-qt.so.0.1 /usr/lib/x86_64-linux-gnu/org.libfcitx-qt.so.0.1

$ sudo cp ./fcitx-qt5/libfcitx-qt5.so.0.1 /usr/lib/x86_64-linux-gnu/libfcitx-qt.so.0.1

참고 사항

참조한 일본 블로그에는 환경 변수 설정 같은 것들이 있다. 그렇지만 .bashrc에 입력기 환경변수를 넣으면 X-Window의 실행 순서 때문에 환경변수가 제대로 동작하지 않을 수도 있다. 편하고도 안전한 방법은 먼저 fcitx 패키지를 설치하는 것이다. 이 글은 fcitx 패키지가 먼저 설치된 것을 전제로 했다.

일본 블로그에서 설명하듯이 기존 파일들을 대체하지 않고도 새로 build한 Qt5 immodule을 사용할 수도 있는데 runtime library가 두 개라 환경 설정을 제대로 해주어야 한다(기존 패키지와의 충돌때문에 설정이 다소 복잡하므로 세부 설명은 안한다). 다만, Qt5 immodule을 복사해야 할 폴더는 /opt/OpenSrc/Qt/5.4/gcc_64/plugins/platforminputcontexts가 아니고, /opt/OpenSrc/Qt/Tools/QtCreator/bin/plugins/platforminputcontexts이더라. 뭐, 두 곳에 다 복사해도 상관은 없다.

2015/05/05

우분투 15.04 fcitx-hangul 설치 및 설정


우분투 15.04에서 기본 입력기인 ibus가 큰 문제가 있는 건 아니지만, 최근에 Qt5를 배우면서 Qt5 입력기 모듈을 제공하는 fcitx를 설치해 보기로 했다. ibus나 uim 입력기는 Qt5 애플리케이션에서 한글 입력시 띄어쓰기 등의 문제가 발생한다. ibus의 경우 이 문제에 대해 버그 리포팅 한지 1년이 다 돼가는데 Qt5.5 이후에나 사용할 수 있을 듯 하다.

참고로, ibus나 fcitx 주 개발자는 중국인이고 uim은 일본인이다. ibus-hangul과 nabi 개발자는 한국 사람인데 그가 만든 libhangul을 fcitx-hangul 입력기에서 사용하고 있다. 그래서 그런지 몰라도 ibus-hangul에서 발생하는 마지막 입력글자가 마우스 이동시 따라 다니는 문제는 fcitx-hangul에서도 발생한다. uim-byeoru를 만든이도 또 다른 한국인인듯 하다. 한글 입력기의 문제는 한국 개발자들이 주개발자가 아니고 한글 입력기 모듈만을 만든는데서 비롯되는 것인지도 모른다. 워낙에 개발자 저변이 열악하다 보니 한번 만들면 유지보수나 사용자 지원이 잘 이루어지지 않고 있는 것이 현실이다.

fcitx를 설치 후 사용해 본 바로는 현재까지의 입력기들 중에선 우분투 유니티 데스크탑 환경에서 가장 좋은 한글입력기라고 말하고 싶다. 기본적으로 유니티 상단 패널에서 한영 전환 상태를 구분할 수 있고, 한자 입력은 물론이고 virtual keyboard를 이용해서 일본어나 특수문자 입력도 가능하다. Qt5 입력 모듈이 기본 제공되고 있어 Qt5 애플리케이션에서도 한글입력이 잘 된다.


fcitx 설치

아래와 같이 fcitx-hangul 패키지만 설치하면 필요한 패키지들이 모두 설치된다.

$ sudo apt-get install fcitx-hangul

패키지 설치 후 기본 입력기인 ibus대신 아래 화면과 같이 [System Settings] > [Language Support]에서 [Keyboard input method system:]의 [fcitx]를 선택한 후, 다시 로그인해야 한다.


한글 키보드 추가 및 한영키 설정

재로그인 후, fcitx 설정 프로그램을 실행해야 한다. 유니티 패널 상단의 키보드를 클릭하여 Configure를 선택하거나, <Super> Key를 눌러서 유니티 대쉬에서 fcitx를 검색해서 나오는 [fcitx Configuration] 아이콘을 선택해서 기동해도 되고, 터미널에서 아래 명령으로 실행해도 된다.

$ fcitx-config-gtk3

먼저, 아래 화면과 같이 [Input Method] 탭 하단 왼쪽의 [+] 버튼을 눌러 Hangul 키보드를 검색한 후 선택하여 추가해 준다. 참고로, 내 경우엔 우분투 기본언어를 영어로 사용하고 있어서, 한글을 기본 언어로 사용하는 경우에는 반대로 Keyboard-English(US)를 추가해야 할지도 모르겠다. 그리고, 두개의 키보드가 추가된 상태에서 위에 있는 키보드가 기본 키보드가 된다. 즉, Hanul 키보드가 위에 있도록 순서를 조정하면 터미널 등의 애플리케이션 실행 후 한글 입력 상태가 된다.


참고로, 이렇게 추가된 키보드는 [System Settings] > [Text Entry]에서도 확인할 수 있는데 한글 키보드는 Hangul (Fcitx)로 표시된다.

이제, 한영전환키로 한영키만 설정해 주면 된다. 아래 화면과 같이 [Global Config] 탭에서 [Trigger Input Method]에 [Ctrl+Space]와 [Empty]로 표시되어 있는 버튼을 각각 눌러서 [한/영]키와 [Shift+Space]키로 바꿔주면 된다.


추가 설정

위의 과정까지만 해 주어도 fcitx를 입력기로 쓰는데 큰 지장은 없는데, 아래 화면과 같이 한글로 전환시 [Hangul]이라고 표시되는 작은 창이 뜨는게 좀 귀찮아서 없애기로 하였다.


위의 화면에 없애는 방법도 같이 보여주고 있는데, gedit로 ~/.config/fcitx/config 파일을 열어서 "ShowInputWindowAfterTriggering=False" 부분을 설정해서 저장한 후,

$ gedit ~/.config/fcitx/config

아래 명령으로 fcitx를 재기동 해주면 된다.

$ fcitx -r

Virtual Keyboard 사용

맨 위의 스크린 샷에 보이는 virtual keyboad를 사용하려면 유니티 상단 패널의 키보드나 태극문양 아이콘을 클릭해서 [Toggle Virtual Keyboard]를 선택하면 키보드가 나타나고, 한번더 선택하면 사라진다. 마우스로 Virtual Keyboard의 [Latin] 부분을 선택하면 번갈아 가면서 일본어 등 다른 키보드들을 선택할 수 있다. 또한, 마우스로 특수문자를 입력해도 되고 해당 키보드가 선택된 상태에서 실제 키보드를 입력해도 해당 키의 특수문자가 입력된다.

fcitx 자잘한 문제들

앞서 언급했던 ibus-hangul과 동일한 한글 마지막 입력글자가 마우스 따라다니는 문제 외에, 우분투 VirtualBox 게스트에서 영문 모드 상태의 유니티 Dash에서 한영키가 동작하지 않아 한글이 입력되지 않는 문제가 있었다. 우분투 호스트에서는 그런 문제는 안생기고 있다. 또, 한가지는 ibus-hangul에서도 발생하는 문제인데, 영문모드 상태에서 구글 chrome 브라우저를 실행하자마자 주소창에서 한영키를 누를 경우 한영전환이 안되는 문제가 있다. 주소창이 아닌 검색창에서 한영키를 누르면 한영전환이 잘 된다.

추가적으로, 다시 ibus를 사용하고자 할 경우 [System Settings] > [Language Support]에서  [fcitx] 대신 [ibus]를 선택하고 재로그인 해 주면 되는데, 한영키를 사용하려면 Hangul (ibus) 키보드를 다시 추가해 주어야 한다. 이때 불필요한 fcitx 프로세스 하나가 같이 살아있는 문제도 있다.

2015/05/01

Qt5 Mouse Event 처리


Qt5도 배울 겸 재미삼아 마우스 이벤트 처리 프로그램을 만들어 본다. UI 프로그램을 거의 짜 본적이 없어서 그런지 마우스 이벤트 처리가 생각보다 복잡하다. 소시적부터 관심이 있었으면 Libre Office Impress 같은 것을 만들었을지도... 도형 들을 마우스로 선택하고 옮기고 크기를 조절하고 Widget 창 크기 조절시 창크기에 비례해서 도형 크기도 변해야 한다. 마우스 커서도 선택 모드, 드래그 모드, 리사이즈 모드에 따라 바꿔주고... 우분투의 Screenshot으로 캡춰했더니 마우스 커서는 모양이 안바뀌네...

그런데, Qt에서 사각형 Class인 QRect가 좀 웃긴다. QRect(x, y, width, height)로 생성할 수 있는데 x2 = x+width-1, y2 = y+height-1로 정의하고 있다. 가령, rect.x2()나 rect.y2()를 호출하면 각각 1pixel이 모자라게 된다. 그런데 놀라운 것은 painter.drawRect(rect)로 그리면 1pixel이 모자라지 않게 잘 그려준다. 별 문제가 없어 보이지만, 계산을 할때는 x2 = x+width, y2 = y+height로 계산해야 1pixel에 대해 제대로 고려가 된다. 또, 한가지 이로 인해 파생되는 문제는 rect.normalized()라는 함수가 width나 height가 음수일 때 양수로 사각형 좌표를 바꿔주는데 x와 x2를 swapping 해버림으로써 사각형 크기가 어긋나게 된다.

1pixel 차이 가지고 뭐 그리 쫀쫀하게 구냐고 할지도 모른다. 예전에 MS Powerpoint 발표자료 만들때 그룹 resize 같은 거 하면 pixel이 어긋나는 한 두놈이 꼭 생겨서 그거 맟추느라 고생했던 기억이 남아 있다. 왜냐하면 1pixel이라도 어긋나면 시청자에게 굉장히 허술하게 보이기 때문에... 최근 버전의 Powerpoint에서는 도형을 움직이거나 resize 하면 근처 도형에 정렬할 수 있도록 기능이 강화된 듯하다. 하지만 근본적인 문제가 해결된 것은 아니었다. 이 문제는, 표를 만들 때 무식한 방법으로 사각형 하나를 복제해서 한 줄을 만든 뒤 이 줄을 다시 복제해서 여러 줄을 만들고, 그룹 resize 후에 cell 들을 정렬시키고 나서 Powerpoint Window 화면을 resize해 보면, 금방 확인해 볼 수 있다. 표의 경계선 굵기가 일정하지 않게 되는 문제가 생긴다. Libre Office Impress에서는 resize시에도 정렬이 잘 되는 편이다.

그리고, Qt는 개발자에게 모든 필요한 것을 제공해 주려는 의도가 강해 보인다. 잡다하게 많은 Class 들이 뭐 썩먹을 수야 있지만 좀 난잡해 보인달까... 가령, int 버전의 QRect와 qreal(float) 버전의 QRectF와 같이 종류별로 int 버전과 qreal 버전을 구비하고 있다. 화면에서의 pixel들은 궁극적으로 int형이긴 하지만 resize나 reshaping 같은 것을 하기 위해서는 qreal 버전이 필요하기는 하다. 위의 pixel이 어긋나는 문제가 생기는 원인도 실수를 정수로 변환하면서 발생하는 rounding 오류 때문에 생긴다. 해결 방법은 가능한한 rounding 오류가 발생하지 않도록 해주면 된다. 가령, 특정 정수의 배수를 사용하여 나머지가 항상 0이 되도록 만들어 준다든지...


Mouse Press Event
void ChartWidget::mousePressEvent(QMouseEvent* event)
{
    m_firstPos = boundPos(event->pos());
    m_lastPos = m_firstPos;

    bool newPos = true;
    bool selectStat = false;
    bool modKey = event->modifiers().testFlag(Qt::ShiftModifier);
    bool itemSelected = m_selectedItems.size() ? true : false;
    QVariant info;

    if(itemSelected) {
        // if mod key pressed, deselect the selected item.
        if(modKey) {
            foreach(Drawable* drawable, m_selectedItems) {
                if(drawable->contains(m_firstPos, &info)) {
                    removeSelectedItem(drawable);
                    selectStat |= drawable->onDeselect();
                    newPos = false;
                    break;
                }
            }
        }
        else {
            // if reclick one of selected items, check new mouse event.
            foreach(Drawable* drawable, m_selectedItems) {
                if((m_resizeIndex = drawable->selectIndex(m_firstPos))) {
                    m_resizeMode = true;
                    m_mouseItem = drawable;
                    return;
                }
                if(drawable->contains(m_firstPos, &info)) {
                    m_dragMode = true;
                    return;
                }
            }
            // if new mouse position, deselect all the selected items.
            foreach(Drawable* drawable, m_selectedItems) {
                removeSelectedItem(drawable);
                selectStat |= drawable->onDeselect();
            }
        }
    }

    if(!itemSelected || newPos) {
        Drawable* clicked = drawableAt(m_firstPos, &info);
        // if new item is clicked select the item.
        if(clicked) {
            m_selectMode = false;
            m_dragMode = true;
            addSelectedItem(clicked);
            selectStat |= clicked->onSelect(event, modKey, info);
        }
        // if no item is selected check new select event.
        else m_selectMode = true;
    }

    if(selectStat) emit selectionChanged();
    if(!m_selectMode) reDraw();
    QWidget::mousePressEvent(event);
}
Mouse Move Event
void ChartWidget::mouseMoveEvent(QMouseEvent* event)
{
    m_lastPos = boundPos(event->pos());

    m_cursor = mcPointer;
    if(m_selectedItems.size()) {
        if(m_resizeMode) {
            m_cursor = (m_resizeIndex < 3) ? mcSizeLCross :
                       (m_resizeIndex < 5) ? mcSizeRCross :
                       (m_resizeIndex < 7) ? mcSizeVert :
                       mcSizeHoriz;
            m_selectRect = m_mouseItem->region();
            foreach(Drawable* drawable, m_selectedItems) drawable->onResize(m_resizeIndex);
            m_selectRect = m_mouseItem->region();
        }
        else if(m_dragMode) {
            m_cursor = mcDrag;
            foreach(Drawable* drawable, m_selectedItems) drawable->onMouseMove(event);
            m_firstPos = m_lastPos;
        }
        else {
            foreach(Drawable* drawable, m_selectedItems) {
                if((m_resizeIndex = drawable->selectIndex(m_lastPos))) {
                    m_cursor = (m_resizeIndex < 3) ? mcSizeLCross :
                               (m_resizeIndex < 5) ? mcSizeRCross :
                               (m_resizeIndex < 7) ? mcSizeVert :
                               mcSizeHoriz;
                    break;
                }
                if(drawable->contains(m_lastPos)) {
                    m_cursor = mcDrag;
                    break;
                }
            }
        }

    }

    reDraw();
    QWidget::mouseMoveEvent(event);
}
void Drawable::onMouseMove(QMouseEvent* event)
{
    Q_UNUSED(event)
    
    QRect rect = region();
    QPoint pos = rect.topLeft() + owner()->lastPos() - owner()->firstPos();
    setRegion(QRect(pos, rect.size()));
}
Mouse Release Event
void ChartWidget::mouseReleaseEvent(QMouseEvent* event)
{
    m_lastPos = boundPos(event->pos());
    //m_lastPos = adjustPosition(m_lastPos);

    bool modKey = event->modifiers().testFlag(Qt::ControlModifier);

    // if mouse left-click position changed.
    if(event->button()==Qt::LeftButton && (m_firstPos-m_lastPos).manhattanLength()>TOL_SELECT) {
        if(!modKey && m_selectedItems.size()) {
            foreach(Drawable* drawable, m_selectedItems) drawable->onMouseRelease(event);
        }
        else {
            bool selectStat = false;
            QVariant info = 0;
            QRect rect = QRect(m_firstPos, m_lastPos);
            foreach(Layer* layer, m_layers) {
                foreach(Drawable* drawable, layer->children()) {
                    // if non-selected items are in mouse dragged region, select new items.
                    if(rect.contains(drawable->region())) {
                        addSelectedItem(drawable);
                        selectStat |= drawable->onSelect(event, false, info);
                    }
                }
            }
            if(selectStat) emit selectionChanged();
        }
    }

    m_selectMode = false;
    m_dragMode = false;
    m_resizeMode = false;
    m_resizeIndex = 0;
    if(m_mouseItem) m_mouseItem = 0;
    reDraw();
    QWidget::mouseReleaseEvent(event);
}
void Drawable::onMouseRelease(QMouseEvent* event)
{
    Q_UNUSED(event)
    
    setRegion(positiveRect(m_rect));
    QVector2D ar = owner()->aspectRatio();
    m_rectOrigin.setWidth(m_rect.width()/ar.x());
    m_rectOrigin.setHeight(m_rect.height()/ar.y());
}
Widget Resize Event
void ChartWidget::resizeEvent(QResizeEvent* event)
{
    if(m_pixmap) delete m_pixmap;
    m_pixmap = new QPixmap(event->size());

    setViewportChanged();
    setViewport(rect());
    qreal arX = m_viewport.width() / m_viewportOrigin.width();
    qreal arY = m_viewport.height() / m_viewportOrigin.height();
    setAspectRatio(arX, arY);

    reDraw();
    setViewportChanged(false);
}
void Drawable::update()
{
    QVector2D ar = owner()->aspectRatio();
    qreal arX = ar.x();
    qreal arY = ar.y();
    if(owner()->viewportChanged()) {
        qreal x1 = arX * m_posOrigin.x();
        qreal y1 = arY * m_posOrigin.y();
        qreal x2 = x1 + arX * m_rectOrigin.width();
        qreal y2 = y1 + arY * m_rectOrigin.height();
        m_posCurrent = QPointF(x1, y1);
        // rounding errors are increased if you rsize chartwidget frequently.
        m_rect = QRect(RoundToMultiple(x1), RoundToMultiple(y1), 
                       RoundToMultiple(x2-x1), RoundToMultiple(y2-y1));
    }
    else {
        m_posCurrent = QPointF(m_rect.x(), m_rect.y());
        m_posOrigin = QPointF(m_posCurrent.x()/arX, m_posCurrent.y()/arY);
    }
}