Linux cogvm xim howto

From 흡혈양파의 인터넷工房
Jump to navigation Jump to search
linux 에서 CogVM 을 compile 하고 XIM 을 사용하는법


ImmX11Plugin 에 대한 사전지식

요즘 배포하는 OneClick 버전들에는 ImmX11Plugin 이 포함되어있지 않다. 이게 없어서 한글입력에 문제가 있나 싶어 이래저래 찾아봤더니 이전에 배포하던 Pharo 에는 있다고 하는것이 아닌가. 그래서 자료를 좀 찾아보기로 했다.


http://www.findthatzip-file.com/search-38773188-hZIP/winrar-winzip-download-pharo-1.1-oneclick.zip.htm


위 사이트의 내용을 보면 pharo 1.1 에서는 so.ImmX11Plugin 이라는 파일이 존재한다는것을 알 수 있다. 그래서 해당되는 파일을 찾아보기로 했다.


https://gforge.inria.fr/frs/?group_id=1299&release_id=5325


이 파일을 받았는데.. 정말있다? 하지만.....

Pharo 1.4 에는 없다.....



그래서 인터넷을 뒤지던중 재미있는걸 하나 발견했다.


http://www.mirandabanda.org/cogblog/downloads/


흠... 현재 cogvm 이 squeak 4.1 이상부터는 적용되어있다는거. 호오.. 그럼....이라고 생각하고 뒤지던중.. 최신버전의 fedora 에 ImmX11plugin 이 적용되어있는거같은 글을 본거같더라. 그럼 찾아봐야곘지?


http://fedora.mirror.nexicom.net/linux/releases/test/20-Alpha/Fedora/source/SRPMS/s/squeak-vm-4.10.2.2614-8.fc20.src.rpm


역시 유명 배포판은 좋다. 잘되어있네....(마이너한 젠투같으니...) 내부에 있는 파일들을 들여다보니.. 일단 ImmX11Plugin 디렉토리가 있다. 오호라.... 이제부터 중요한건 정말로 이 squeak 패키지 파일이 cogvm 과 관련이 있느냐 하는것이다.


CogVM 에 대한 작업의 시작

일단 srpm 파일은 둘째치고 cogvm 소스를 확인해보도록 했다. 사실 smalltalk-kr 의 내용도 개인적으로 관심이 있었기에 이기회에 한번 시작해보자는 마음을 먹었다. (이 문서의 끝 부분에 나오겠지만.. 이건 정말로 잘 한 선택이라고 생각된다)


http://www.mirandabanda.org/cogblog/downloads/


위 페이지에서 설명하는대로 CogVM 소스를 받도록 한다. 방법은 간단하다.

svn co http://www.squeakvm.org/svn/squeak/branches/Cog


작업을 진행해보려고 하다가 그래도 젠투인데.. 하면서 gentoo 에서 뭔가 할 수 있을거같은 생각이 들었다.


https://bitbucket.org/erosa/erosa-overlay/src/cbc54c104306/dev-smalltalk/?at=default


오호.. ebuild 를 받아서 local portage 를 구성하도록 한다. 근래에 뭔가 gentoo 의 rule 이 바꼈다. 아래와같은 부분을 추가해주어야 한다.

echo "dev-smalltalk" >> /etc/portage/categories


manifest 등을 진행하고 emerge(compile) 을 시작한다. 일단 비교를 위해서 pharo 와 squeak 둘 다 설치해보았다.



이야 잘된다. 다만.. 좀 낭패인게 squeak 의 vm 은 cmake 가 적용되어있지만 Pharo 는 binary package 만 있기에 확인이 불가능. 그래도.. 메세지는 비슷한거같다.


# /usr/lib/pharo/pharo --help

.......
  -pathenc <enc>        set encoding for pathnames (default: UTF-8)
  -plugins <path>       specify alternative plugin location (see manpage)
  -textenc <enc>        set encoding for external text (default: UTF-8)
  -version              print version information, then exit
  -vm-<sys>-<dev>       use the <dev> driver for <sys> (see below)
  -trace[=num]          enable tracing (optionally to a specific value)
  -codesize <size>[mk]  set machine code memory to bytes
  -tracestores          enable store tracing (assert check stores)
  -cogmaxlits <n>       set max number of literals for methods compiled to machine code
  -cogminjumps <n>      set min number of backward jumps for interpreted methods to be considered for compilation to machine code
  -blockonerror         on error or segv block, not exit.  useful for attaching gdb
Deprecated:
  -notimer              disable interval timer for low-res clock
  -display <dpy>        quivalent to '-vm-display-X11 -display <dpy>'
  -headless             quivalent to '-vm-display-X11 -headless'
  -nodisplay            quivalent to '-vm-display-null'
  -nomixer              disable modification of mixer settings
  -nosound              quivalent to '-vm-sound-null'
  -quartz               quivalent to '-vm-display-Quartz'

Notes:
  <imageName> defaults to `pharo.image'.
  Using `unix:0' for <dpy> may improve local display performance.
  -xshm only works when Squeak is running on the X server host.
  If `-memory' is not specified then the heap will grow dynamically.
  <argument>s are ignored, but are processed by the pharo image.
  The first <argument> normally names a pharo `script' to execute.
  Precede <arguments> by `--' to use default image.

Available drivers:
  vm-sound-null
  vm-display-null
  vm-display-X11


/usr/lib/squeak/4.10.2-2614/squeakvm --help

.......
  -pathenc <enc>        set encoding for pathnames (default: UTF-8)
  -plugins <path>       specify alternative plugin location (see manpage)
  -textenc <enc>        set encoding for external text (default: UTF-8)
  -version              print version information, then exit
  -vm-<sys>-<dev>       use the <dev> driver for <sys> (see below)
Deprecated:
  -display <dpy>        quivalent to '-vm-display-X11 -display <dpy>'
  -headless             quivalent to '-vm-display-X11 -headless'
  -nodisplay            quivalent to '-vm-display-null'
  -nosound              quivalent to '-vm-sound-null'
  -quartz               quivalent to '-vm-display-Quartz'

Notes:
  <imageName> defaults to `squeak.image'.
  -vtlock disables keyboard vt switching even when -vtswitch is enabled
  Using `unix:0' for <dpy> may improve local display performance.
  -xshm only works when Squeak is running on the X server host.
  If `-memory' is not specified then the heap will grow dynamically.
  <argument>s are ignored, but are processed by the Squeak image.
  The first <argument> normally names a Squeak `script' to execute.
  Precede <arguments> by `--' to use default image.

Available drivers:
  vm-sound-null
  vm-sound-ALSA
  vm-sound-OSS
  vm-sound-custom
  vm-display-custom
  vm-display-null
  vm-display-fbdev
  vm-display-X11


뭐 driver 의 문제는 있지만 기본적으로는 동일한게 아닐까라는 생각을 했다. 만약 squeak 이 CogVM 이라면 ebuild 에서 CogVM 이 compile 가능하다는 얘기가 되니.... vmmaker 부터 차근차근히 cogvm 의 compile 을 진행해 보도록 하려고 했다. (결국은 squeak 은 CogVM 이 아니었던거같다)


사실 squeak 의 소스등을 뒤져서 plugin 에 대한 내용을 알아보려했으나... 기존의 nqsqueak 도 vmmaker 를 통해서 생성되는거라는 결론에 이르렀다. 일단 squeak 의 소스를 살펴보기로 마음먹은 이후 npsqueak 의 ./unix/src/vm/interp.h 파일을 살펴보니 상단부에

VMMAKER_VERSION "4.10.2"


이런 내용이 있다. 흠.. 어차피 vmmaker 가 뭔지 정확하게 알고가야할 필요는 있다는 생각이 들기 시작했다.


http://pharo.gemtalksystems.com/book/Virtual-Machine/Building/VMMakerTool/


CogVM. 본격적인 compile 진행

일단 vmmaker 를 이용한 CogVM 의 build 과정에는 다음과같은 것들이 필요하다.

  • git
  • cmake
  • make
  • gcc
  • zip


cogvm platform sources 디렉토리의 구조를 보자

  • / 기본디렉토리 되시겠다.
  • /platforms/ 모든 플랫폼에서 사용되는 공통코드. 각 플랫폼 대상의 특정코드, 설정파일,도구 등이 개별 디렉토리에 나뉘어 들어가 있다.
  • /platforms/Cross/
  • /platforms/Mac OS/
  • /platforms/RiscOS/
  • /platforms/unix/
  • /platforms/win32
  • /src/ VMMaker 를 통해서 만들어진 소스코드
  • /build/cmake 설정파일에 사용되는 디렉토리. 실제 make 등의 명령어를 이용해서 빌드를 시작한다.
  • /results/ 만들어진 VM 이 저장되는 곳
  • /image/ generator 이미지를 만들기 위한 Pharo 이미지를 넣고 작업하는곳
  • /codegen-scripts/ 필수 또는 유용하게 사용되는 스크립트들이 들어있는곳


이후의 내용은 pdf 파일과 http://www.squeakvm.org/unix/devel.html 내용을 참고로한다.


git 에서 cogvm 용 환경받기

git 에서 cogvm 용 compile environment 를 받는다.


git clone git://gitorious.org/pharo-build/pharo-build.git


귀찮다면 다음의 파일을 받아서 압축을 풀어도 된다.


http://strepo.onionmixer.net/pharo-build_original_20131213.tar


받아진 디렉토리로 이동하기

받아진 compile environment 디렉토리로 이동한다.

cd pharo-build


vmmaker 를 위한 추가파일 받아서 세팅하기

다음의 파일들을 받아서 아래처럼 이름을 변경한다. 파일은 "pharo-build" 디렉토리에 받으면 된다.

# mv cog_20131212.tar.gz cog.tar.gz
# mv vmmaker-image_20131212.zip vmmaker-image.zip


CogVM 빌드용 스크립트 파일의 교체

스크립트 파일을 교체한다. 기존의 스크립트 파일은 몇가지 우려되는 부분이 있기때문에 스크립트 파일의 일부분을 수정했다. "cogvm-build.sh" 라는 파일을 다음의 내용으로 교체해주면 된다.

!bash -lex
#
# build-vm.sh -- Builds Cog Virtual Machine. Have to be used together with Jenkins.
#
# Copyright (c) 2012 Christophe Demarey
#

#http://files.pharo.org/vm/src/cog.tar.gz
#http://files.pharo.org/vm/src/vmmaker-image.zip

# Set environment
PROCESS_PLUGIN="UnixOSProcessPlugin"
ZIP_FILTER='*'
SQUEAKVM="./pharo2.0/pharo"


if [ "$OS" == "" ]; then
        OS="linux"
fi


if [ "$OS" == "win" ]; then
    set -e # Stop the execution if a command fails!
        CONFIGNAME="CogWindowsConfig"
    PROCESS_PLUGIN="Win32OSProcessPlugin"
    ZIP_FILTER=' *.exe *.dll'
elif [ "$OS" == "mac" ]; then
    CONFIGNAME="CogCocoaIOSConfig"
    export MACOSX_DEPLOYMENT_TARGET=10.5
elif [ "$OS" == "linux" ]; then
    CONFIGNAME="CogUnixConfig"
fi


echo "===Compile OS target :: $OS"

# Unpack sources
echo "===unpack platform source"
cd "$WORKSPACE"

rm -rf cog
tar xzf cog.tar.gz || echo # echo needed for Windows build (error on symlinks creation)

mkdir -p cog/build
mkdir -p cog/image

echo "===unpack smalltalk image for vmmkaer"
cd cog/image
unzip -o ../../vmmaker-image.zip


# Generate sources
echo "===Genarate vmmaker source from image"
echo "$CONFIGNAME new
  addExternalPlugins: #( FT2Plugin );
  addInternalPlugins: #( $PROCESS_PLUGIN );
  generateSources; generate.

Smalltalk snapshot: false andQuit: true." > ./script.st
"$SQUEAKVM" -headless generator.image script.st -headless

echo "===pack vmmaker source"
cd ../../
tar -czf ${CONFIGNAME}-sources.tar.gz -C cog .


# Compile
echo "===start compile CogVM"
cd cog/build
if [ "$OS" == "win" ]; then
  cmake -G "MSYS Makefiles" .
else
  cmake .
fi
make
cd ../results

# Archive
echo "===pack CogVM"
COG_ZIP_FILE="Cog-${OS}.zip"

zip -r $COG_ZIP_FILE $ZIP_FILTER
mv -f $COG_ZIP_FILE ../../

#if [ "$OS" == "mac" ]; then
#    zip -r ../build/CogVM.zip CogVM.app
#    mv -f ../build/CogVM.zip ../../
#else
#    zip -r Cog.zip $ZIP_FILTER
#    mv Cog.zip ../../
#fi

# success
echo "===all process finished! Conguration!"
exit 0


위의 스크립트를 돌리기전에 SQUEAKVM 에 선언된 VM 실행파일 디렉토리에는 PharoV20.sources 파일이 같이 있어야 함을 명심하자. 어차피 이 스크립트를 돌리기 위해서는 Pharo-linux 2.0 이 하나는 있어야 한다.


CogVM 빌드의 시작

스크립트 파일을 실행한다. 실행은 다음과 같다.

# sh cogvm-build.sh


compile 된 CogVM 결과물의 확인

만들어진 cogvm 파일을 확인한다. Cog-linux.zip 이라는 파일이고 파일안에 있어야 하는 내용은 다음과 같다.

CogVM
libB3DAcceleratorPlugin.so
libFT2Plugin.so
libInternetConfigPlugin.so
libJPEGReadWriter2Plugin.so
libJPEGReaderPlugin.so
libRePlugin.so
libSqueakFFIPrims.so
vm-display-X11
vm-display-null
vm-sound-ALSA
vm-sound-null


사실 cogvm-build.sh 파일의 내부를 보면 알겠지만... "pharo-build/cog/results" 이 안에 compile 한 파일이 모두 들어있다. 이 디렉토리와 같은 수준의 파일들이면 성공이라고 봐도 되겠다.

이제 CogVM compile 은 성공적으로 마무리되었다.


CogVM 을 이용한 pharo 의 구동

linux 상에서 Pharo 를 사용하기 위해서는 다음과같은 스크립트를 만든후 각 환경에 맞게 편집해서 사용하는것이 좋다.

#!/bin/bash

# path
DIR=`readlink -f $0` #resolve symlink
ROOT=`dirname $DIR` #obtain dir of the resolved path
LINUX="$ROOT/Contents/Linux"
RESOURCES="$ROOT/Contents/Resources"

# icon (note: gvfs-set-attribute is found in gvfs-bin on Ubuntu
# systems and it seems to require an absolute filename)
gvfs-set-attribute \
        "$0" \
        "metadata::custom-icon" \
        "file://$RESOURCES/Pharo.png" \
                2> /dev/null

# zenity is part of GNOME
image_count=`ls "$RESOURCES"/*.image 2>/dev/null |wc -l`
if which zenity &>/dev/null && [ "$image_count"  -ne 1 ]; then
        image=`zenity --title 'Select an image' --file-selection --filename "$RESOURCES/" --file-filter '*.image' --file-filter '*'`
else
        image="$RESOURCES/Pharo-2.0-one-click.image"
fi

# execute
exec "$LINUX/pharo" \
        -plugins "$LINUX" \
        -encoding utf8 \
        -vm-display-X11 \
        -compositioninput \
        "$image"


ImmX11Plugin 빌드를 위한 사전준비

하드디스크에 squeak 와 CogVM 의 소스를 준비한다.

  • ./Squeak-3.10-5/platforms/unix/src/plugins
  • ./pharo-build/cog/src/plugins


일단 CogVM 부터는 cmake 라는 시스템을 사용한다. cmake 는 근래들어 도입되기 시작한 multiplatform 을 위한 make system으로서 unix make 처럼 Makefile 을 이용한 compile 을 진행하는것이 목적이 아니라 unix 등의 platform 에 맞는 build 환경을 구축하게 해주는것이 목적이다. 더 자세한 내용을 알고싶다면 아래의 주소를 참고하도록 한다.

http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/Development/Env/cmake


자 그럼 CogVM 에서 cmake 를 사용한다고 했는데 cmake 의 사용시 가장 기본이 되는 "CMakeLists.txt" 파일을 살펴봄으로서 ImmX11Plugin 의 빌드준비를 해보기로 하자. 기존의 ImmX11Plugin 은 gnu make 에만 대응되어 있기때문에 CogVM 의 빌드에 끼워넣기 위해서 지금부터 하는 작업은 필수라고 할 수 있다. 일단 아래의 2개 파일부터 살펴보자

  • ./cog/build/CMakeLists.txt
  • ./cog/build/FT2Plugin/CMakeLists.txt


위의 2개의 CMakeLists.txt 파일중 build 바로 아래쪽에 있는게 master CMakeLists.txt 파일이다. 이걸 염두에 두고 ImmX11Plugin 의 cmake 를 위해 linux 상에 반드시 존재할거같은 FT2Plugin 을 기준으로 살펴보기로 하겠다.


FT2Plugin 을 대상으로 plugin 이라는게 대체 어디어디 위치해 있는지... 그리고 안쪽에는 대체 뭐가 들었는지를 좀 살펴보기로 하겠다.

gentoo32 pharo-build # find ./ -name FT2Plugin
./cog/platforms/iOS/plugins/FT2Plugin
./cog/platforms/win32/plugins/FT2Plugin
./cog/src/plugins/FT2Plugin
./cog/build/FT2Plugin


오호... 4개정도가 나오는거같다. 그럼 각 디렉토리별로 어떤 파일이 들어있는지를 알아보기로 하자.


./cog/platforms/iOS/plugins/FT2Plugin

FT2Plugin.xcodeproj
Info-FT2Plugin.plist
PkgInfo
freetype2
macFileNameBits.c

iOS 디렉토리는 xcode 프로젝트 파일 및 mac os X 에서 해당 plugin 을 compile 하기위한 파일들이 들어있다. header 파일이 같이 있는것이 특징이다.


./cog/platforms/win32/plugins/FT2Plugin

makeFreetype.dynamic.win32


내용을 보면 sheel script 가 들어가있다. freetype에 대한 dll 파일을 받아오는 부분이 있는것이 특징이다.

./cog/src/plugins/FT2Plugin

FT2Plugin.c

CogVM 의 plugin 으로 사용되기위한 c source code 가 들어있다. 이 부분이 실질적으로 plugin 의 본체라고 보면 되겠다.


./cog/build/FT2Plugin

CMakeLists.txt

cmake 에서 사용하는 설정내용이 들어가있다. 그 내용을 좀 살펴보기로 하자.


message("Adding external plugin: FT2Plugin")
set(pluginName "FT2Plugin")
set(pluginSrc "${srcPluginsDir}/FT2Plugin")
set(pluginCross "${crossDir}/plugins/FT2Plugin")
set(pluginPlatform "${targetPlatform}/plugins/FT2Plugin")
set(LINKLIBS )
list(APPEND sources  "${pluginSrc}/FT2Plugin.c")
include_directories(${pluginSrc} ${pluginCross} ${targetPlatform}/plugins/${pluginName})
list(APPEND LINKLIBS freetype)
include_directories(/usr/include/freetype2)
add_library(FT2Plugin SHARED ${sources})
set(LIBRARY_OUTPUT_PATH ${outputDir})
set(linkFlags "${linkFlags} -m32")
target_link_libraries(FT2Plugin ${LINKLIBS})
set_property(TARGET FT2Plugin PROPERTY LINK_FLAGS "${linkFlags}")
IF (FT2Plugin_dependencies)
add_dependencies(FT2Plugin ${FT2Plugin_dependencies})
ENDIF (FT2Plugin_dependencies)


몇가지 참고할만한 부분이 있다. 그걸 살펴보기로하자.

list(APPEND sources  "${pluginSrc}/FT2Plugin.c")

소스코드의 파일명이 언급되어있다.


include_directories(/usr/include/freetype2)

header 파일에 대한 언급이 되어있다.


사실 이 두가지만해도 큰 정보를 얻었다고 봐도 문제 없을거같다.


이 장의 내용중 가장 큰 목적은 linux 상에서 ImmX11Plugin 을 넣는것이라 할 수 있다. 그럼 그 목적에 충실하기 위해 아래 파일의 내용을 살펴보도록 하자.


===./cog/build/CMakeLists.txt===
# This is automatically generated file using CogUnixConfig on 14 December 2013 3:39:56.18 pm
cmake_minimum_required(VERSION 2.6.2)
project("CogVM")
include(directories.cmake)
message("${CMAKE_MODULE_PATH}")
set(CMAKE_CONFIGURATION_TYPES Release)
include_directories(${targetPlatform}/plugins/B3DAcceleratorPlugin)
include_directories(${crossDir}/vm ${srcVMDir} ${targetPlatform}/vm ${buildDir})
add_definitions( -DLSB_FIRST=1 -DUSE_GLOBAL_STRUCT=0 -DCOGMTVM=0 -m32 -DENABLE_FAST_BLT  -g0 -O2 -fno-tree-pre -fno-caller-saves -msse2 -D_GNU_SOURCE -DNDEBUG -DITIMER_HEARTBEAT=1 -DNO_VM_PROFILE=1 -DDEBUGVM=0)
add_custom_command(OUTPUT version.c
                COMMAND ${platformsDir}/unix/config/verstamp version.c gcc
                COMMENT "Generating version.c"
        )
set(coreSources  ${srcVMDir}/cogit.c ${srcVMDir}/gcc3x-cointerp.c)
set(platformVMSources  ${targetPlatform}/vm/aio.c ${targetPlatform}/vm/debug.c ${targetPlatform}/vm/osExports.c ${targetPlatform}/vm/sqUnixCharConv.c ${targetPlatform}/vm/sqUnixExternalPrims.c ${targetPlatform}/vm/sqUnixHeartbeat.c ${targetPlatform}/vm/sqUnixMain.c ${targetPlatform}/vm/sqUnixMemory.c ${targetPlatform}/vm/sqUnixThreads.c ${targetPlatform}/vm/sqUnixVMProfile.c)
set(crossVMSources  ${crossDir}/vm/sqHeapMap.c ${crossDir}/vm/sqTicker.c ${crossDir}/vm/sqExternalSemaphores.c ${crossDir}/vm/sqNamedPrims.c ${crossDir}/vm/sqVirtualMachine.c)
set(extraSources version.c)
add_executable(CogVM  ${coreSources} ${crossVMSources} ${platformVMSources} ${extraSources})
add_subdirectory("ADPCMCodecPlugin")
add_subdirectory("AsynchFilePlugin")
add_subdirectory("B2DPlugin")
add_subdirectory("BitBltPlugin")
add_subdirectory("BMPReadWriterPlugin")
add_subdirectory("CroquetPlugin")
add_subdirectory("ZipPlugin")
add_subdirectory("DropPlugin")
add_subdirectory("DSAPrims")
add_subdirectory("FFTPlugin")
add_subdirectory("FileCopyPlugin")
add_subdirectory("FilePlugin")
add_subdirectory("FloatArrayPlugin")
add_subdirectory("FloatMathPlugin")
add_subdirectory("IA32ABI")
add_subdirectory("JoystickTabletPlugin")
add_subdirectory("Klatt")
add_subdirectory("LargeIntegers")
add_subdirectory("Matrix2x3Plugin")
add_subdirectory("MIDIPlugin")
add_subdirectory("MiscPrimitivePlugin")
add_subdirectory("Mpeg3Plugin")
add_subdirectory("SecurityPlugin")
add_subdirectory("SerialPlugin")
add_subdirectory("SocketPlugin")
add_subdirectory("SoundCodecPrims")
add_subdirectory("SoundPlugin")
add_subdirectory("StarSqueakPlugin")
add_subdirectory("SurfacePlugin")
add_subdirectory("LocalePlugin")
add_subdirectory("UnixOSProcessPlugin")
add_dependencies(CogVM B3DAcceleratorPlugin)
add_subdirectory("B3DAcceleratorPlugin")
add_dependencies(CogVM SqueakFFIPrims)
add_subdirectory("SqueakFFIPrims")
add_dependencies(CogVM JPEGReaderPlugin)
add_subdirectory("JPEGReaderPlugin")
add_dependencies(CogVM JPEGReadWriter2Plugin)
add_subdirectory("JPEGReadWriter2Plugin")
add_dependencies(CogVM RePlugin)
add_subdirectory("RePlugin")
add_dependencies(CogVM InternetConfigPlugin)
add_subdirectory("InternetConfigPlugin")
add_dependencies(CogVM FT2Plugin)
add_subdirectory("FT2Plugin")
target_link_libraries(CogVM  ADPCMCodecPlugin AsynchFilePlugin B2DPlugin BitBltPlugin BMPReadWriterPlugin CroquetPlugin ZipPlugin DropPlugin DSAPrims FFTPlugin FileCopyPlugin FilePlugin FloatArrayPlugin FloatMathPlugin IA32ABI JoystickTabletPlugin Klatt LargeIntegers Matrix2x3Plugin MIDIPlugin MiscPrimitivePlugin Mpeg3Plugin SecurityPlugin SerialPlugin SocketPlugin SoundCodecPrims SoundPlugin StarSqueakPlugin SurfacePlugin LocalePlugin UnixOSProcessPlugin)
set_target_properties(CogVM PROPERTIES LINK_FLAGS "-m32")
set_source_files_properties( ${srcVMDir}/cogit.c PROPERTIES 
                COMPILE_FLAGS "-O1 -fno-omit-frame-pointer -momit-leaf-frame-pointer -mno-rtd -mno-accumulate-outgoing-args")
set_source_files_properties( ${targetPlatform}/vm/sqUnixHeartbeat.c PROPERTIES 
                COMPILE_FLAGS "-O1 -fno-omit-frame-pointer -mno-rtd -mno-accumulate-outgoing-args")
list(APPEND LINKLIBS m)
list(APPEND LINKLIBS dl)
list(APPEND LINKLIBS pthread)
set(EXECUTABLE_OUTPUT_PATH "/root/cogvm_scripts/pharo-build/cog/results")
add_subdirectory("vm-display-null")
add_subdirectory("vm-display-X11")
add_subdirectory("vm-sound-ALSA")
add_subdirectory("vm-sound-null")
target_link_libraries(CogVM ${LINKLIBS})


뭔가 위에서 봤던 CMakeLists.txt 파일과는 상당이 틀린 분량의 내용을 자랑하고 있다. 그렇다고 안할수는 없지 않은가? 살펴봐야할 부분들을 살펴보기로하자.

add_dependencies(CogVM FT2Plugin)
add_subdirectory("FT2Plugin")


오호 이렇게 해서 일단 cmake 의 target chain 에 추가되는 상태라고 짐작이 된다. ...........external plugin 은 이게 끝이다.(야!)


뭐 일단 혹시 모르니 다른파일을 좀 살펴보도록 하자.

./cog/src/examplePlugins.ext


오호.. 안쪽에 뭔가 막 적혀있다. 여기에도 뭔가를 적어줘야 할거같은 느낌이 들지 않는가?


자 뭔가 파일들은 막 살펴본거같은데 이제부터 뭘 해야될지 고민을 좀 해야할거같다.


ImmX11Plugin 을 위한 cmake 의 준비

일단 이전의 squeak 소스에 있는 ImmX11Plugin 을 복사부터 해와야 맞는거같다.


./cog/src/plugins/


위의 디렉토리에 ImmX11Plugin 의 소스코드를 복사해보도록 하자.


그리고 아래와 같은 디렉토리를 만들자


./cog/build/ImmX11Plugin/


디렉토리 내의 CMakeLists.txt 파일을 다음과 같이 구성하도록 하자.

message("Adding external plugin: ImmX11Plugin")
set(pluginName "ImmX11Plugin")
set(pluginSrc "${srcPluginsDir}/ImmX11Plugin")
set(pluginCross "${crossDir}/plugins/ImmX11Plugin")
set(pluginPlatform "${targetPlatform}/plugins/ImmX11Plugin")
set(LINKLIBS )
list(APPEND sources  "${pluginSrc}/ImmX11Plugin.c")
include_directories(${pluginSrc} ${pluginCross} ${targetPlatform}/plugins/${pluginName})
list(APPEND LINKLIBS freetype)
add_library(ImmX11Plugin SHARED ${sources})
set(LIBRARY_OUTPUT_PATH ${outputDir})
set(linkFlags "${linkFlags} -m32")
target_link_libraries(ImmX11Plugin ${LINKLIBS})
set_property(TARGET ImmX11Plugin PROPERTY LINK_FLAGS "${linkFlags}")


이제 아래의 파일을 편집해볼 차례다


===./cog/build/CMakeLists.txt'''===

add_subdirectory("ImmX11Plugin")


이 Link 내용을 파일안쪽에 새로 추가해 주도록 하자.


이제는 "cmake ." 을 실행해서 제대로 ImmX11Plugin 이 추가되었는지를 확인해볼 차례다.

gentoo32 build # cmake .
-- The C compiler identification is GNU 4.7.3
-- The CXX compiler identification is GNU 4.7.3
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done

Adding internal plugin: ADPCMCodecPlugin
Adding internal plugin: AsynchFilePlugin
Adding internal plugin: B2DPlugin
Adding internal plugin: BitBltPlugin
Adding internal plugin: BMPReadWriterPlugin
Adding internal plugin: CroquetPlugin
Adding internal plugin: ZipPlugin
Adding internal plugin: DropPlugin
Adding internal plugin: DSAPrims
Adding internal plugin: FFTPlugin
Adding internal plugin: FileCopyPlugin
Adding internal plugin: FilePlugin
Adding internal plugin: FloatArrayPlugin
Adding internal plugin: FloatMathPlugin
Adding internal plugin: IA32ABI
Adding internal plugin: JoystickTabletPlugin
Adding internal plugin: Klatt
Adding internal plugin: LargeIntegers
Adding internal plugin: Matrix2x3Plugin
Adding internal plugin: MIDIPlugin
Adding internal plugin: MiscPrimitivePlugin
Adding internal plugin: Mpeg3Plugin
Adding internal plugin: SecurityPlugin
Adding internal plugin: SerialPlugin
Adding internal plugin: SocketPlugin
Adding internal plugin: SoundCodecPrims
Adding internal plugin: SoundPlugin
Adding internal plugin: StarSqueakPlugin
Adding internal plugin: SurfacePlugin
Adding internal plugin: LocalePlugin
Adding external plugin: ImmX11Plugin
Adding internal plugin: UnixOSProcessPlugin
Adding external plugin: B3DAcceleratorPlugin
Adding external plugin: SqueakFFIPrims
Adding external plugin: JPEGReaderPlugin
Adding external plugin: JPEGReadWriter2Plugin
Adding external plugin: RePlugin
Adding external plugin: InternetConfigPlugin
Adding external plugin: FT2Plugin
Adding module: vm-display-null
Adding module: vm-display-X11
Adding module: vm-sound-ALSA
Adding module: vm-sound-null
-- Configuring done
-- Generating done
-- Build files have been written to: /root/cogvm_scripts/pharo-build/cog/build


운좋게 목록에는 보인다. compile 을 진행해보자.

...
Linking C shared library /root/cogvm_scripts/pharo-build/cog/results/libImmX11Plugin.so
[ 97%] Built target ImmX11Plugin
Scanning dependencies of target vm-display-null
[ 98%] Building C object vm-display-null/CMakeFiles/vm-display-null.dir/root/cogvm_scripts/pharo-build/cog/platforms/unix/vm-display-null/sqUnixDisplayNull.c.o
/root/cogvm_scripts/pharo-build/cog/platforms/unix/vm-display-null/sqUnixDisplayNull.c:176:1: warning: initialization from incompatible pointer type [enabled by default]
/root/cogvm_scripts/pharo-build/cog/platforms/unix/vm-display-null/sqUnixDisplayNull.c:176:1: warning: (near initialization for ‘display_null_itf.winOpen’) [enabled by default]
...


어라 ...........됐다?


Compile 된 ImmX11Plugin 의 테스트

이제 Pharo2.0.image 파일로 Pharo 를 구동한다. font 를 세팅하고 한글을 입력해본다. 물론! XIM을 지원하는 입력기가 떠있어야 하는건 당연한 소리 되겠다. 이야 안된다.

Smalltalk loadModule: 'ImmX11Plugin'


pharo 의 workspace 내부에서 위의 코드를 doit 한다음....


Smalltalk listLoadedModules. #('ImmX11Plugin 10 November 2008 (e)' 'ZipPlugin VMMaker-oscog-LucFabresse.306 (i)' 'FT2Plugin Freetype-Plugin-IgorStasenko.64 (e)' 'B2DPlugin VMMaker-oscog-LucFabresse.306 (i)' 'BitBltPlugin VMMaker-oscog-LucFabresse.306 (i)' 'LocalePlugin VMMaker-oscog-LucFabresse.306 (i)' 'SecurityPlugin VMMaker-oscog-LucFabresse.306 (i)' 'FilePlugin VMMaker-oscog-LucFabresse.306 (i)' 'LargeIntegers v1.5 VMMaker-oscog-LucFabresse.306 (i)' 'MiscPrimitivePlugin VMMaker-oscog-LucFabresse.306 (i)')


이렇게 확인해도 안된다. 한글입력이 개선되지는 않는다. 흐음.. 그럼 이제부터 plugin 이 제대로 작동되는지부터 확인을 해봐야겠다.


일단 바로 위의 module list 부터 살펴보자.

'ImmX11Plugin 10 November 2008 (e)'


오호.. ImmX11Plugin.c 소스코드에서 관련된 부분을 찾아보자.

#ifdef SQUEAK_BUILTIN_PLUGIN
	"ImmX11Plugin 10 November 2008 (i)"
#else
	"ImmX11Plugin 10 November 2008 (e)"
#endif


squeak-pharo 에서 plugin 은 internal 과 external 로 나뉜다(driver와는 틀리다) 여기서 ImmX11Plugin 은 external 로 취급받는다는것을 알 수 있다.


그럼 다시 plugin 의 C source 를 살펴보자. 다음과같은 부분이 있을것이다.

EXPORT(sqInt) primGetEncoding(void);
EXPORT(sqInt) primGetLocale(void);
EXPORT(sqInt) primGetLocaleEncoding(void);
EXPORT(sqInt) primGetPathEnc(void);
EXPORT(sqInt) primGetTextEnc(void);
EXPORT(sqInt) primGetXWinEnc(void);
EXPORT(sqInt) primIsTextEncUTF8(void);
EXPORT(sqInt) primSetCompositionFocus(void);
EXPORT(sqInt) primSetCompositionWindowPosition(void);
EXPORT(sqInt) primSetEncoding(void);
EXPORT(sqInt) primSetEncodingToLocale(void);
EXPORT(sqInt) primSetLocale(void);
EXPORT(sqInt) primSetLocaleEncoding(void);
EXPORT(sqInt) primSetPathEnc(void);
EXPORT(sqInt) primSetPathEncToLocale(void);
EXPORT(sqInt) primSetTextEnc(void);
EXPORT(sqInt) primSetTextEncToLocale(void);
EXPORT(sqInt) primSetTextEncUTF8(void);
EXPORT(sqInt) primSetXWinEnc(void);
EXPORT(sqInt) primSetXWinEncToLocale(void);
EXPORT(sqInt) setInterpreter(struct VirtualMachine* anInterpreter);
EXPORT(sqInt) shutdownModule(void);


함수들의 prototype 이 선언되어있다. 이 함수들은 smalltalk 상에서 Primitive object 인 ImmX11Plugin 의 메서드로 동작하게 되어있다. 자 이제부터 하나하나 좀 뒤져보기로 할까?


Multilingual-Languages > LnaguageEnvironment > KoreanEnvironment > class side > systemConverterClass

이 메서드의 안쪽을 살펴보기로 하자.

systemConverterClass
	| encoding |
	OSPlatform isWin32 
		ifTrue: [^EUCKRTextConverter].
	OSPlatform isMacOS
		ifTrue: [^UTF8TextConverter].
	OSPlatform isUnix 
		ifTrue: [encoding := X11Encoding encoding.
			encoding ifNil: [^EUCKRTextConverter].
			(encoding = 'utf-8') 
				ifTrue: [^UTF8TextConverter].							
			^EUCKRTextConverter].
	^UTF8TextConverter


오호... OSPlatform 이라는 객체를 찾을 수 있다. 이제 OSPlatform 이라는 객체를 Browse 해보도록 하자. 그렇게하면 current 라는 메서드를 class side 에서 찾을 수 있다. 그럼 workspace 를 하나열고 다음과같은 코드를 doit 해보도록 하자.

OSPlatform current  an UnixPlatform


나는 UnixPlatform 이라고 하는 결과가 나왔다. 그럼 하나 더 살펴보기로 할까? 아까의 OSPlatform 내의 메서드중에 windowSystemName 이라는 메서드가 있다. 이걸 실행해보면 어떤게 나올까?

OSPlatform windowSystemName  'X11'


이야! X11 이라는 string 이 반환되었다. 이로서 위쪽의 systemConverterClass 에서 사용하는 Logic 의 조건판단에서 쓰이는 부분은 다 알아내었다. 지금부터 봐야할건 조건문을 trace 하는법이 되겠다.


위의 조건에서 ImmX11Plugin 이 들어있는 X11Encoding 으로 어떻게 건너가는지에 대해 조건을 하나하나 추적해보도록 하자. 일단 첫째조건부터 보도록 하자.

OSPlatform isUnix  true


일단 내 환경은 unix 가 맞다고 한다. 그렇다면 ifTrue 는 만족한다고 봐야할거고.. 그 다음의 내용을 보도록 하자.

encoding := X11Encoding encoding.
encoding ifNil: [^EUCKRTextConverter].


"X11Encoding encoding." 의 값을 encoding 이라는 변수에 넣는다. 그럼 X11Encoding 을 browse 해보도록 하자. 일단 instance side 에는 아무것도 없고 class side 에서 뭔가 확인할 수 있다. 하지만 명심하자 smalltalk 에서 class 라고해서 그건 instance 화 되지 않는 객체가 아니다. smalltalk 에 로딩되는 순간부터 이미 그건 class 의 basic instance 로 존재한다는 사실을 기억해야한다. X11Encoding 의 class side 에는 몇가지의 메서드들이 있다. 이중에서 위의 코드에 언급된 encoding 이라는 메서드를 살펴보기로 하자.

encoding

	| enc |
	enc := self getEncoding.
	enc ifNil: [ ^ nil ].
	^ enc asLowercase.


이야.. getEncoding 을 호출한다. 그럼 getEncoding 메서드를 보기로 하자.

getEncoding
	<primitive: 'primGetEncoding' module: 'ImmX11Plugin'>
	^ nil

딱걸렸으! 우리가 그렇게 쑤셔넣을려고하는 primitive object 가 나왔다. 여기까지면 어떤식으로 ImmX11Plugin 이 불러지는지는 확인한거같고.. 다시 위쪽의 ifTrue 를 사용하는 부분으로 돌아가기로 하자. 한번 첫번째 줄을 workspace 에서 doit 해보기로 할까?

encoding := X11Encoding encoding.  'utf-8'


이야? 일단 primitive object 는 정상적으로 동작하는거같다. 왜냐하면 아까전의 X11Encoding 을 Browse 해서 추적할때 우리는 utf-8 이라는 문자열을 확인할 수 없었다. 그런데 지금은 확인가능. 문제는 이게 CogVM 실행파일에서 인수로 준것의 영향을 받는건지를 지금시점에서 알 수는 없지만.. 일단 동작을 한다는게 중요하다. 그렇다면 아까의 KoreanEnvironment 에서 encoding 변수는 Nil 이 아니기때문에 EUCKR 과는 전혀 상관없는 동작을 하게될거다.


그럼 그 다음의 몇줄을 살펴보도록 하자.

			(encoding = 'utf-8')
				ifTrue: [^UTF8TextConverter].
			^EUCKRTextConverter].


오호.. 다른언어의 if 에 해당하는 평가식이 있다. 그럼 이 평가식을 동작시켜보도록 할까?

(encoding = 'utf-8')  true


오호..true 가 나왔다. 그럼 이 아래의 ifTrue 구문은 반드시 동작하게 되어있다는 의미가 되겠다. 그럼 결과적으로 systemConverterClass 는 UTF8TextConverter 를 반환하게 되어있다는 얘기가 된다.


자.. 추론은 이제 됐고 실행으로 한번 옮겨보자.

KoreanEnvironment systemConverterClass  UTF8TextConverter


오호... 이제 원하는 값 까지는 나온거같다. 그럼 ImmX11Plugin 이 먹지않는 경우를 테스트해보기로 하자.

KoreanEnvironment systemConverterClass  EUCKRTextConverter


자 반환되는 결과값이 "EUCKRTextConverter" 가 된다. 일단 이 결과를 봤을때 ImmX11Plugin 은 제대로 작동한다는 의미인거같다. 그럼 이제부터는 뭘 테스트해봐야할까.. 원래대로라면 입력기에서 입력되는 값 자체를 후킹해봐야하는거같다.


Pharo 에서의 XIM 이상동작을 확인하기

지금까지 primitive 객체(ImmX11Plugin)의 동작은 확인했다. 그렇다면 왜 한글입력이 안되는것일까? 이제부터 뭘 알아보면 좋을까.. 일단 아까 작동을 테스트해보던 systemConverterClass 를 부르는 녀석들부터 찾아보는게 좋을거같다. Finder 에서 systemConverterClass 를 source 를 대상으로 찾아보기로 한다. 그럼 2개정도가 나온다. finder 에서 검색의 결과값은 아래와 같다.

initilize
▷ EncodedCharSet class

languageEnvironment
▷ EUCKRTextConverter


자 이제 관련된것들을 하나하나 뒤져보기로 하자.(이제는 무엇을 위해 어디를 뒤지는지도 목적이 애매해지고있다) 일단 EUCKRTextConverter 부터 시작해보기로 하자.


EUCKRTextConverter 는 다음에 소속되어있는 클래스로서 2개의 인스턴스 메서드와 1개의 클래스 메서드를 가진다.

Multilingual-TextConversion > TextConverter > EUCTextConverter > EUCKRTextConverter


이 클래스에 소속된 메서드는 다음과 같다.

instance method

  • languageEnvironment
  • leadingChar


class method

  • encodingNames


이중에서 KoreanEnvironment 를 사용하고 있는건 languageEnvironment 가 되겠다.


EncodedCharSet 클래스는 다음에 소속된 클래스로서 몇가지의 메서드를 가지고 있다.

Multilingual-Encodings > EncodedCharset


이 클래스의 메서드중에서 KoreanEnvironment 를 사용하고 있는던 class 메서드중 initialize 이다. 코드를 적어보면 다음과같은 내용이 된다.

initialize
"
	self initialize
"
	self allSubclassesDo: [:each | each initialize].

	EncodedCharSets := Array new: 256.

	EncodedCharSets at: 0+1 put: Unicode "Latin1Environment".
	EncodedCharSets at: 1+1 put: JISX0208.
	EncodedCharSets at: 2+1 put: GB2312.
	EncodedCharSets at: 3+1 put: KSX1001.
	EncodedCharSets at: 4+1 put: JISX0208.
	EncodedCharSets at: 5+1 put: JapaneseEnvironment.
	EncodedCharSets at: 6+1 put: SimplifiedChineseEnvironment.
	EncodedCharSets at: 7+1 put: KoreanEnvironment.
	EncodedCharSets at: 8+1 put: GB2312.
	"EncodedCharSets at: 9+1 put: UnicodeTraditionalChinese."
	"EncodedCharSets at: 10+1 put: UnicodeVietnamese."
	EncodedCharSets at: 12+1 put: KSX1001.
	EncodedCharSets at: 13+1 put: GreekEnvironment.
	EncodedCharSets at: 14+1 put: Latin2Environment.
	EncodedCharSets at: 15+1 put: RussianEnvironment.
	EncodedCharSets at: 17+1 put: Latin9Environment.
	EncodedCharSets at: 256 put: Unicode.

EncodedCharSets 을 초기화할때 스스로를 배열로 만들고 배열안에 데이터를 집어넣는것이 기본 핵심이다.


XIM 을 위한 Event 를 추적하기

자 조금 접근방식을 다르게 해보자. 일단 Finder 에서 KeyStroke 라는걸 source 검색으로 검색하면 많은것들이 나온다 그중에서 keyCharacter 아래의 KeyboardEvent 라는걸 보도록 하자 그렇게하면 nautilus 에서 다음과같은 protocol 까지 접근할 수 있다.


Morphic-Events > MorphicEvent > UserInputEvent > KeyboardEvent > keyboard(protocol)


이 안쪽에 보면 몇가지 메서드를 찾을 수 있다. 여기서 확인할 수 있는 메서드는 다음과 같다.

  • keyCharacter
  • keyString
  • ketValue
  • scanCode


오호.. 웬지 쓸만해보이는게 몇가지 있다. 이 메서드들을 테스트해보기 위해 새로운 morph 를 만들어서 key관련된 event 를 처리하는것으로 해보겠다. 일단 아래와 같은 명령을 workspace 에서 doit 해보도록 하자.

keytestmorph := 'Key test Morph' asMorph openInWorld.

Pharo XIM 01.png


이야!! 화면 왼쪽 상단 구석에 "Key test Morph" 라고 써져있는 String Morph 가 생겨났다. 이걸 좀 보기 편한곳으로 mouse 를 이용해서 끌어다 놓은다음 다음과같은 명령을 진행해보자

keytestmorph contents: '한글 참 힘드네!!!'

Pharo XIM 02.png


이야... morph 에서 보이는 text 의 내용이 바꼈다. 이제부터는 머리를 좀 써보도록 하자. 화면에 보이는 morph 는 "keytestmorph" 라는 이름을 가진 StringMorph 가 되겠다. finder 를 이용해서 찾는 방법도 있겠지만 여기서는 좀 더 smart 한 방법을 쓰도록 하자. ctrl-i 를 이용해서 inpector 창을 연다. 여기서 self 부분을 보면 a StringMorph 부분을 볼 수 있다. 이걸 mouse 로 선택한 다음 ctrl-b 를 눌러서 browse 하게되면 바로 StringMorph 를 nautilus 로 browse 할 수 있다. 이제부터 잘 생각을 해보자 위에서 살펴보던 MorphicEvent 는 사실 Object 의 subclass 가 되겠다. Squeak 에서 화면에 보이는 모든것은 morph 이기 때문에 morph 보다 더 상위에 존재하는 계층이라 보면 될거같다. 그러면 방금전에 살펴본 StringMorph 의 경우를 살펴보자. StringMorph 는 Morph 를 상속받아서 만들어진 class 다. 고로 StringMorph 에서 굳이 override 하지 않는다면? 결과적으로 위쪽에서 선언된 메서드는 사용이 가능하다고 봐도 되겠다. 그래서 StringMorph 에서는 MorphicEvent 에 선언된 메서드는 특별한 일이 없다면 바로 사용할 수 있다고 봐도 무방하겠다.


SBE 책을 살펴보면 CrossMorph 를 언급하는 부분에서 handlesMouseDown: 이라는 부분을 사용하는 경우가 나온다. 여기서는 keyboard 입력을 테스트하기 위한것이 강하기 때문에 일단 handlesMouseDown: 을 찾아서 관련된 부분을 더 찾아가 보기로 한다.


finder에서 handlesMouseDown: 을 찾으면 EventHandler 라는 부분이 있는걸 알 수 있다. 이걸 browse 해보도록 하자 그럼 다음과 같은 내용을 볼 수 있다.

Pharo XIM 03.png


그렇다면 다음의 구조를 nautilus 에서 살펴보도록 하자.


Morphic-Kernel > Morph > event handling(protocol) > handlesMouseDown:


오호.. 이 바로위에 좋은게 보인다.

  • handlesKeyDown:
  • handlesKeyStroke:
  • handlesKeyUp:


아마도 key 눌림에 대한 이벤트 인거같다. 그럼 다음의 시도록 해보도록 하자. (어차피 instance method 라서 큰 상관은 없을거같다)

keytestmorph handlesKeyStroke: anEvent  false.
keytestmorph handlesKeyDown: anEvent  false.
keytestmorph handlesKeyUp: anEvent  false.


아.. 역시 날로 먹는건 되지 않는다. 사실 SBE 문서에서는 별도로 class 를 선언한다음 그걸로 작업을 하라고 되어있다. 우리는 class 를 선언한게 아니라 StringMorph 를 이용해서 instance 만 생성했기때문에 안되는듯 하다.


XIM 입력값 테스트를 위한 테스트용 class 의 제작 :: KeyTestMorph

StringMorph 를 직접 손댔다가는 무슨일이 벌어질지 모르니 여기서도 StringMorph 를 상속받은 class 를 하나 만들어보기로 한다. 겸사겸사 keytestmorph 를 새로 만들어지는 class 의 이름으로 만들고 기존에 있는 instance 는 destroy 하기로 한다. 다음의 과정을 통해서 만들어진 StringMorph 를 해제할 수 있다.

keytestmorph delete.
Smalltalk garbageCollect.


linux 의 terminal 에서 하는 sync 와 비슷하다. CogVM 에서 알아서 해주겠지만 가끔 쓰레기통을 비워주는 일은 효율면에서 중요하다. 이제 다음의 코드를 통해 class 를 만들어보기로 하자.


여기서는 class 의 선언과 해당되는 class 에서 사용하게될 instance method 를 보고 참고해서 class 를 만들도록 한다. 아래 문서를 참고해서 class 를 만들었다.

http://wiki.squeak.org/squeak/3503
handlesKeyboard: evt
	"comment stating purpose of message"

	^ true.
handlesMouseOver: anEvent 
	"comment stating purpose of message"

^true
keyStroke: anEvent
	"key input code to self contents"

	| keyValue |
	keyValue := anEvent keyString.
keyboardFocusChange: aBoolean
	"comment stating purpose of message"

	"aBoolean 
		ifTrue: [ Transcript show: 'Got Focus' ]
		ifFalse: [ Transcript show: 'Lost Focus']."
mouseEnter: anEvent 
	"comment stating purpose of message"

	anEvent hand newKeyboardFocus: self.
	"Transcript show: 'damm'"
mouseLeave: anEvent 
	"comment stating purpose of message"

	anEvent hand newKeyboardFocus: nil


자 여기서 주의할 부분이 있다. keyStroke 부분을 보자. 정작 keyValue 가 반환하는것은 anEvent 의 keyString 이다. 이제 만들어진 클래스로 인스턴스를 생성하고 만들어진 morph 위에 mouse 를 올려놓은다음 키보드로 입력을 해보자. 영문 3 글자 한글 3 글자를 입력하도록 한다.

Pharo XIM 04.png


오? keyString 에서 넘어온 값을 Transcript 로 출력을 했는데 깨진 한글이 나온다. 나이쓰! 일단 그럼 keyString 을 추적해야하지 않을까?


그럼 일단 keyString 을 추적해보도록 하자. Finder 에서 keyString 을 selector 로 찾으면 2개가 나오는데 그중에서 위쪽의 것을 열어보면 다음과같은것을 확인할 수 있다.


Morphic-Events > MorphicEvent > UserInputEvent > KeyboardEvent > keyboard(protocol) > keyString


이 메서드의 내용은 다음과같다.

keyString
	"Answer the string value for this keystroke. This is defined only for keystroke events."

	^ String streamContents: [ :s | self printKeyStringOn: s ]


키 입력에 따른 string 값을 반환한단다. 키보드 이벤트에만 반응한다고 하고. 일단 다른건 몰라도 anEvent 는 아마도 MorphicEvent 의 더 상위단이 아닌가 싶다. 뭐 여튼 내용을 보면 streamContents 으로 String 을 반환하는걸로 보인다. 다른건 크게 신경쓰이지 않는데 printKeyStringOn: 이라는게 신경이 좀 쓰이니 찾아보기로 하자. 역시 KeyboardEvent 클래스 안의 메서드 되시겠다.

printKeyStringOn: aStream
	"Print a readable string representing the receiver on a given stream"

	| kc inBrackets firstBracket keyString |
	kc := self keyCharacter.
	inBrackets := false.
	firstBracket := [ inBrackets ifFalse: [ aStream nextPut: $<. inBrackets := true ]].
	self controlKeyPressed ifTrue: [ 	firstBracket value. aStream nextPutAll: 'Ctrl-' ].
	self commandKeyPressed ifTrue: [ firstBracket value. aStream nextPutAll: 'Cmd-' ].
	(buttons anyMask: 32) ifTrue: [ firstBracket value. aStream nextPutAll: 'Opt-' ].
	(self shiftPressed and: [ keyValue between: 1 and: 31 ])
		ifTrue: [ firstBracket value. aStream nextPutAll: 'Shift-' ].

	(self controlKeyPressed and: [ keyValue <= 26 ])
			ifTrue:
				[aStream nextPut: (keyValue + $a asciiValue - 1) asCharacter]
			ifFalse: 
				[keyString := (kc caseOf: {
					[ Character space ] -> [ ' ' ].
					[ Character tab ] -> [ 'tab' ].
					[ Character cr ] -> [ 'cr' ].
					[ Character lf ] -> [ 'lf' ].
					[ Character enter ] -> [ 'enter' ].

					[ Character backspace ] -> [ 'backspace' ].
					[ Character delete ] -> [ 'delete' ].

					[ Character escape ] -> [ 'escape' ].

					[ Character arrowDown ] -> [ 'down' ].
					[ Character arrowUp ] -> [ 'up' ].
					[ Character arrowLeft ] -> [ 'left' ].
					[ Character arrowRight ] -> [ 'right' ].

					[ Character end ] -> [ 'end' ].
					[ Character home ] -> [ 'home' ].
					[ Character pageDown ] -> [ 'pageDown' ].
					[ Character pageUp ] -> [ 'pageUp' ].

					[ Character euro ] -> [ 'euro' ].
					[ Character insert ] -> [ 'insert' ].

				} otherwise: [ String with: kc ]).
				keyString size > 1 ifTrue: [ firstBracket value ].
				aStream nextPutAll: keyString].

	inBrackets ifTrue: [aStream nextPut: $> ]


이런저런 잡다한 내용이 많지만 그건 알바 아니고... 보면 내부에서 여러가지 특스 키들에 대한 처리를 하고있다. 아까의 key 입력테스트용 morph 에서 "backspace" 키를 눌러본적이 있는가? 그렇게하면 "<backspace>" 라는 문자열이 Transcript 에 보여지는걸 확인할 수 있다. 그런처리를 여기서 하게 되는거다. 사실 제일 중요한건 첫째줄에 있다. 이 메서드는 입력을 aStrem 으로 받는다는 사실이다. 그렇게되면 아까의 keyString 에서 보였던 streamContents: 부분이 실제 데이터가 넘어오는 부분이라는 얘기가 된다. 그럼 streamContents: 를 finder 에서 찾아보기로 하자. selector 로 찾으면 몇개 정도가 나오는데 그중에서 눈에 띄는 streamContents: 부분에 대해 생각해보도록 하자.


일단 아까 만든 class 인 KeyTestMorph 의 keyStoke 메서드 내부를 다음과 같이 수정한다.

keyStroke: anEvent
	"key input code to self contents"

	| keyValue |
	keyValue := anEvent keyString.
	
	(anEvent isKeystroke)
	ifTrue: [Transcript show: 'key stroke!!'; cr.
		Transcript show: anEvent class; cr.].


일단 Key의 입력이 넘어오는 anEvent 가 어떤 클래스인지부터 알아보자. 위의 코드를 입력하고 인스턴스에서 아무키나 눌러서 테스트를 진행하면 Transcript 에 다음과같은 내용이 나오게 된다.

key stroke!!
KeyboardEvent


키가 눌려지는것을 확인할 수 있고 key 를 넘겨주는 객체는 KeyboardEvent 라는것을 알 수 있다. (이건 XIM 에서 한/영 양쪽 다 같은 결과가 나온다)


XIM 입력값 추적을 위한 이벤트의 계층추적 및 Unicode class 의 동작테스트

흠.... KeyboardEvent 는 위쪽에서 이미 한번 살펴본바가 있는 class 이다. 아무래도 이 클래스 자체가 뭔가를 받는거같다. 일단 KeyboardEvent 에서 좀 간단해보이는 keyChracter 를 살펴보기로 하자. keyChracter 메서드의 내부 코드는 다음과 같다.

keyCharacter
	"Answer the character corresponding this keystroke. This is defined only for keystroke events."

	^Unicode value: charCode.


오호.. unicode 를 return 한다. charCode 를 받아서 말이다. 그럼 여기서 Transcript 로 charCode 를 출력해보면 뭔가 알 수 있지 않을까? 일단 charCode 가 어떤 클래스인지부터 알아보기로 하겠다. 아래처럼 코드를 수정한다.

keyCharacter
	"Answer the character corresponding this keystroke. This is defined only for keystroke events."

	Transcript show: charCode class; cr.
	^Unicode value: charCode.


Transcript 에서 다음과같은 출력결과를 확인할 수 있다.

...
SmallInteger
SmallInteger
SmallInteger
SmallInteger
SmallInteger
SmallInteger


SmallInteger 가 작살나게 나온다. 이야.. 죽이는데? 이제 charCode 의 값을 뿌려볼 차례다.

keyCharacter
	"Answer the character corresponding this keystroke. This is defined only for keystroke events."

	Transcript show: charCode class; cr.
	^Unicode value: charCode.


특히 한영전환을 해서 실험해보도록 하자.

227
227
227
227
227
227
key stroke!!
KeyboardEvent
133
133
133
133
133
133
key stroke!!
KeyboardEvent
129
129
129
129
129
129
key stroke!!
KeyboardEvent


뭐지 이건.. 뭔가 엄청나게 괴악한 값이 들어가고 있다. 대체 어디서부터 꼬인걸까. 일단 상상도 못하는 일이 벌어지고 있는거같다. 대체 왜 한국어 한글자를 입력하는데 저렇게 많은 숫자가 등장하며 왜 한번에 키보드 이벤트가 3개나 들어오는걸까...


일단 여기서는 더 건질게 없을거같아서 keyString 을 살펴보기로 했다. keyString 의 코드는 다음과 같다.

keyString
	"Answer the string value for this keystroke. This is defined only for keystroke events."

	^ String streamContents: [ :s | self printKeyStringOn: s ]

일단 String 의 streamContents: 의 결과값을 반환하도록 되어있으며 인자로는 블록식이 주어지고 있다. 블록식 내부에서는 s 를 사용하고 있으며 s 는 KeyboardEvent 의 printKeyStringOn: 이라는 메서드의 반환값을 사용하고 있다. 그럼 위에서 한번 언급한 printKeyStringOn: 이라는 메서드를 조금 더 살펴보기로 하자.


이 메서드의 초반 부분에는 kc := self keyCharacter. 라는 부분이 있다. 오호 다시 뭔가 도로아미타불이 되는건가. 결과적으로 keyString 은 keyChracter 의 결과값을 한번 다듬어서 받는것에 불과한것이다. 그럼 역시 key 는 keyChracter 가 쥐고있게 되는거같다. 다시 keyChracter 를 살펴보자.


^Unicode value: charCode.


키보드로 입력된 charCode 값을 Unicode 값으로 변환해서 반환하는 루틴 되시겠다. 그렴 여기서 Unicode 클래스를 workspace 에서 잠시 테스트해보기로 하자.


한글로 "ㅎ" 에 해당하는 unicode 값은 U+314E 가 된다. 그럼 Unicode 의 value: 메서드를 살펴보기로 하곘다.

value: code

	| l |
	code < 256 ifTrue: [^ Character value: code].
	l := Locale currentPlatform languageEnvironment leadingChar.
	^ Character leadingChar: l code: code.

내용인즉슨 code 라는 값을 인수로 받아서 code 가 256 보다 작은값이면 (SmallInteger니까) Chracter 클래스의 value: 메서드를 이용해서 ascii 값을 반환한다. code 가 만약 256 보다 크다면 "Locale currentPlatform languageEnvironment leadingChar" 의 실행결과를 l 이라는 변수로 집어넣고 Chracter 라는 class 의 이런저런 결과값을 받아서 반환하게 되겠다.


자 그럼 하나하나 디버깅을 다시 해보도록 하자. 일단 위쪽에서 나와있는 코드의 일부분을 workspace 에서 디버깅해보도록 하겠다.

Locale currentPlatform languageEnvironment class  Latin1Environment


현재 언어환경의 반환값은 Latin1Environment 가 되어있다. 흐음 뭔가 좀 미심쩍다. ImmX11Plugin 의 결과는 이게 아니었을거같은데..... 참고로 Latin1Environment class 는 다음에 속해있다.


Multilingual-Languages > LanguageEnvironment > Latin1Environment


오호 여기에는 다음과같은것도 있다.

Multilingual-Languages > LanguageEnvironment > KoreanEnvironment


이 KoreanEnvironment 안에는 class side 에 systemConverterClass 라는 메서드가 있는데 이 메서드 안에는 X11Encoding 이라는 부분이 있으며 이건 이 문서의 중간부분에서 이미 살펴본 부분으로서 현재 정상동작을 하는 부분이라는걸 알 수 있다. 그럼 결과적으로 LanguageEnvironment 자체가 KoreanEnvironment 가 아니어서 문제가 될 수도 있는걸까? 그 사실을 알아보기 위해 정상적으로 Pharo 가 동작하는 platform 에서 같은 실험을 해보도록 하겠다.


어라.. 한글 입력이 잘되는 mac 에서도 Latin1Environment 가 나온다. 그럼 이건 아니라는 얘기 되겠다. 지금의 삽질은 실패다. 그럼 다시 아까의 code 로 넘어가보자.


일단 Unicode 의 value: 에서 code 의 값과 l 의 값을 Transcript 로 출력해보기로 하겠다. Unicode 의 value: 의 내용을 아래와 같이 변경한다.


value: code

	| l |
	code < 256 ifTrue: [^ Character value: code].
	l := Locale currentPlatform languageEnvironment leadingChar.
	Transcript show: code asString; cr; show: l asString; cr; show: 'next'; cr.
	^ Character leadingChar: l code: code.


일단 키보드 입력을 해보자.. 뭐라도 나오겠지... Transcript 의 결과를 보면 다음과 같다.

8364
0
next
8364
0
next
key stroke!!
KeyboardEvent


흠.. 이번에는 한글/영문 다 같은값이다. 8364... 과연 나는 어디로 가고있는걸까... 그래 일단 위에서 하려다가 말아버린 Unicode 클래스의 테스트부터 해보도록 하겠다. (방금전에 추가한 Transcript 는 잠시 주석처리 하도록 하자.)

Unicode value: 97  $a
Unicode value: 12622  $


Pharo XIM 05.png


어라... 잘 나온다... 0x0061 은 10진수로는 97이고 0x314E 는 10진수로 12622 이다. 결과적으로 Unicode 함수 자체는 잘 동작한다는 결론이 되겠다. 그럼 KeyboardEvent 의 keyCharacter 의 charCode 자체가 잘못넘어온다는 얘기가 되는거같다.


keyCharacter 안에서 사용되는 charCode 는 사실 KeyboardEvent class 의 인스턴스 변수다. KeyboardEvent 는 그 자체로 인스턴스로 존재하며 어디선가 KeyboardEvent 클래스에 값을 넘겨주는 곳이 있을거라고 생각된다. 일단 KeyboardEvent 의 = 메서드를 보기로 한다.

= aMorphicEvent
	super = aMorphicEvent ifFalse:[^false].
	buttons = aMorphicEvent buttons ifFalse: [^ false].
	keyValue = aMorphicEvent keyValue ifFalse: [^ false].
	^ true


XIM 입력추적을 위한 Event추적 :: Morph 와 관련된 내용

오호.. 웬지 MorphicEvent class 랑 관련이 있어보인다. 그럼 MorphicEvent 의 = 메서드를 살펴보자.

= anEvent
	anEvent isMorphicEvent ifFalse:[^false].
	^self type = anEvent type


흠 이걸로는 도음이 안되는거같다. MorphicEvent 를 좀더 뒤져보자. 이번에는 class side 를 보도록 하자. 보니깐 readFrom: 이라는 부분이 있다.

type: eventType readFrom: aStream
	^self basicNew type: eventType readFrom: aStream


흠.. 아까의 aMorphicEvent 도 그렇고 aStream 이라는것도 뭔가 냄새가 좀 풍기는거같기도하다. 뭔가 이렇게 찾다보니 소 뒷검을질치듯 뭔가 이상한걸 하나 찾았다.


Morphic-Kernel > Morph > HandMorph > private events(protocol) > generateKeyboardEvent:


위의 메서드가 그것이다. 내용을 좀 살펴보자.

generateKeyboardEvent: evtBuf
	"Generate the appropriate mouse event for the given raw event buffer"

	| buttons modifiers type pressType stamp charCode keyValue keyEvent |
	stamp := evtBuf second.
	stamp = 0 ifTrue: [stamp := Time millisecondClockValue].
	pressType := evtBuf fourth.
	pressType = EventKeyDown
		ifTrue: [
			type := #keyDown.
			lastKeyScanCode := evtBuf third].
	pressType = EventKeyUp ifTrue: [type := #keyUp].
	pressType = EventKeyChar ifTrue: [
		type := #keystroke].
	modifiers := evtBuf fifth.
	buttons := modifiers bitShift: 3.
	keyValue := evtBuf third. 
	charCode := evtBuf sixth.
	
	"Adjustments to provide consistent key value data for different VM's�:
	- charCode always contains unicode code point.
	 - keyValue contains 0 if input is outside legacy range�"
	"If there is no unicode data in the event, assume keyValue contains a correct (<256) Unicode codepoint, and use that"
	(charCode isNil
		or: [charCode = 0])
		ifTrue: [charCode := keyValue].
	"If charCode is not single-byte, we definately have Unicode input. Nil keyValue to avoid garbage values from som VMs."	
	charCode > 255 ifTrue: [keyValue := 0].

	type = #keystroke
		ifTrue: [combinedChar
			ifNil: [
				| peekedEvent |
				peekedEvent := Sensor peekEvent.
				(peekedEvent notNil
					and: [peekedEvent fourth = EventKeyDown])
					ifTrue: [
						(CombinedChar isCompositionCharacter: charCode)
							ifTrue: [
								combinedChar := CombinedChar new.
								combinedChar simpleAdd: charCode asCharacter.
								(combinedChar combinesWith: peekedEvent third asCharacter)
									ifTrue: [^nil].
								]]]
			ifNotNil: [
				(combinedChar simpleAdd: charCode asCharacter)
					ifTrue: [charCode := combinedChar combined charCode].
				combinedChar := nil]].

	(type = #keystroke and: [(buttons anyMask: 16) 
			and: [charCode = 30 or: [charCode = 31]]])
		ifTrue: [^MouseWheelEvent new 
					setType: #mouseWheel
					position: lastMouseEvent cursorPoint
					direction: (charCode = 30 ifTrue: [#up] ifFalse: [#down])
					buttons: buttons
					hand: self
					stamp: stamp].	
	keyEvent := KeyboardEvent new
		setType: type
		buttons: buttons
		position: self position
		keyValue: keyValue
		charCode: charCode
		hand: self
		stamp: stamp.
	keyEvent scanCode: lastKeyScanCode.
	^keyEvent


generateKeyboardEvent: 메서드는 evtBuf 라는것을 메세지로 받는듯하다. 그럼 이제까지 했던대로 Transcript 에서 evtBuf 의 class 를 알아보자. 조사해보면 Array 라는게 나온다. generateKeyboardEvent: 메서드의 중간부분만 발췌해서 살펴보자.

	pressType := evtBuf fourth.
	pressType = EventKeyDown
		ifTrue: [
			type := #keyDown.
			lastKeyScanCode := evtBuf third].
	pressType = EventKeyUp ifTrue: [type := #keyUp].
	pressType = EventKeyChar ifTrue: [
		type := #keystroke].
	modifiers := evtBuf fifth.
	buttons := modifiers bitShift: 3.
	keyValue := evtBuf third. 
	charCode := evtBuf sixth.


일단 지금 보고 있는건 키보드의 입력이벤트에 대한 부분이다. 그리고 evtBuf 는 배열이라는것을 알 수 있다. 배열의 각 element 를 순서별로 사용하는데 별다른일이 없으면 charCode 를 사용하게 된다. 아까 위해서 XIM 으로 한글을 입력하는 경우 적어도 열번이 넘는 입력이 일어난다는걸 알 수 있다. 그럼 이 경우는 generateKeyboardEvent: 메서드 자체가 그만큼 불려진다는거니까 evtBuf 를 그 숫지만큼 넘겨주는 경우가 되겠다. 그럼 HandMorph 자체가 그만큼이나 호출된다는 의미이기도 한거같다. 여튼간에 이벤트가 그만큼 발생한다는 얘긴데....


XIM 입력추적을 위한 HandMorph 의 Event 처리살펴보기

HandMorph 의 상위 클래스인 Morph 클래스의 event handling(protocol) 부분을 보도록 한다. handlesKeyStroke: 메서드를 보면 내부적으로는 handlesKeyboard: 메서드를 사용하는데 이 메서드의 내용을 보도록 하자.

handlesKeyboard: evt
	"Return true if the receiver wishes to handle the given keyboard event"
	self eventHandler ifNotNil: [^ self eventHandler handlesKeyboard: evt].
	^ false


오 갓! 이제 뭔가 찾아가는 기분이 들기도 한다. eventHandler 라는놈이 눈에 띄는데? 이걸 browse 해보도록 하자.


browse 하면 몇가지 class 를 선택할 수 있도록 되어있는데 InputEventHandler 가 웬지 원하는놈일거같은 생각이 든다. 사실 MorphicEventHandler 도 있는데 여기서 알고싶은건 morph 로 전달되기전의 anEvent 에 해당되는 값이니까 사실 InputEventHandler 를 보는게 맞을거라는 생각이 든다. 한번 가보자. InputEventHandler 의 위치는 다음과 같다.


Kernel-Processes > InputEventHandler


여기서 다음을 보도록 하자. 아래는 event(protocol) > handleEvent: 의 내용이다.

handleEvent: eventBuffer
	self subclassResponsibility


이야 일단 eventBuffer 를 필요로 하는구나. (정작 쓰지는 않네) 그리고 initialize-release(protocol) 부분을 보면 registerIn: 이라는 메서드가 있다. 이 내용을 보도록 하자.

registerIn: anEventFetcher
	eventFetcher := anEventFetcher.
	eventFetcher registerHandler: self


여기서 eventFetcher 는 InputEventHandler 의 인스턴스 변수 되시겠다. 그런데 InputEventHandler 의 선언부분에 잘 못보던게 있다. 바로 아래의 문구다.

poolDictionaries: 'EventSensorConstants'


오호.. 뭔가 딕셔너리를 쓴다? 그럼 저건 뭐하는건가.. 알아보기전에 위의 handleEvent: 의 내용중에 subclassResponsibility 라는 부분을 좀 알아보기로 하겠다. 아무래도 신경이 쓰인다. SBE 에도 설명은 되어있는데 뭔소린지따위는 모르겠다 아무래도 자료를 좀 봐야할듯. 일단 squeak 에서는 추상클래스라는 부분에서 설명이 좀 되어있다.


http://wiki.squeak.org/squeak/472


아.. 뭔가 말이 많다 다른 자료를 찾아보자.. 빙고.. 뭔가 정확하게 말이 나온게 있다.


http://www.maartensz.org/computing/squeak/Helps/Glossary/Terms/subclassResponsibility.htm


내용인즉슨 이렇다.


subclassResponsibility 메시지는 superclass 를 추상 class(Abstract Classes) 로 선언할때 이 클래스를 상속받는 서브클래스가 반드시 해당메서드를 구현해야하게끔 할때 사용된다. 사실 smalltalk 은 objective-c 처럼 선언부와 구현부가 분리되어있지 않고 선언에서 바로 구현까지 들어가기때문에 해당되는 메서드의 몸통을 그냥 놔둘수가 없기때문에 이런 방법을 사용하는듯 하다. 참고로 이 경우 이 추상 class 를 상속받은 서브클래스에서 해당되는 메서드의 내부구현을 하지 않는다면 에러가 발생되는것에 유의하자.


실제로 InputEventHandler 를 상속받은 서브클래스인 InputEventSendor 와 UserInterruptHandler class 는 각자 handleEvent: 메서드를 반드시 구현하고 있다는것을 알 수 있다. 이 경우 나타나는 화살표의 방향도 틀리다. browser 의 메서드 부분을 유의해서 보도록 하자.


자 이제 다음으로 진행해 보도록 하자. 위에서 EventSensorConstants 이 딕셔너리로 활용되는것을 확인했다. InputEventHandler 를 상속받은 클래스들도 해당되는 딕셔너리를 지정하고 있다는것을 알 수 있다. browse 를 통해서 해당되는 class 를 살펴보도록 하자.


EventSensorConstants 클래스는 인스턴스 메서드를 가지지 않고 오직 class 메서드만 가지게된다. nautilus 에서 class side 를 보면 initilize 라는 메서드를 확인할 수 있다. 그 내용을 보도록 하자.

initialize
	"EventSensorConstants initialize"
	RedButtonBit := 4.
	BlueButtonBit := 2.
	YellowButtonBit := 1.

	ShiftKeyBit := 1.
	CtrlKeyBit := 2.
	OptionKeyBit := 4.
	CommandKeyBit := 8.

	"Types of events"
	EventTypeNone := 0.
	EventTypeMouse := 1.
	EventTypeKeyboard := 2.
	EventTypeDragDropFiles := 3.
	EventTypeMenu := 4.
	EventTypeWindow := 5.

	"Press codes for keyboard events"
	EventKeyChar := 0.
	EventKeyDown := 1.
	EventKeyUp := 2.
	
	"Window event action codes"
	WindowEventMetricChange := 1. " size or position of window changed - value1-4 are left/top/right/bottom values "
	WindowEventClose := 2. " window close icon pressed "
	WindowEventIconise := 3. " window iconised  or hidden etc "
	WindowEventActivated :=4. " window made active - some platforms only - do not rely upon this "
	WindowEventPaint := 5. " window area (in value1-4) needs updating. Some platforms do not need to send this, do not rely on it in image "


뭔가 사용하게될 기본값들에 대한 이런저런 내용들이 들어있는걸 알 수 있다. 보자보자.. 이거 어디서 좀 본거같은데? 웬지 HandMorph > generateKeyboardEvent: 메서드와 관련이 좀 있을거같다. 하지만 일단 무시하도록 하자.


자 보던거 마저 보도록 하자. InputEventHandler class 에는 registerIn: 이라는 메서드가 있다. 이 내용을 좀 보도록 하자.

registerIn: anEventFetcher
	eventFetcher := anEventFetcher.
	eventFetcher registerHandler: self


InputEventHandler class 는 인스턴스 변수로 eventFetcher 라는것을 가지고 있다. 그리고 위의 코드에서 InputEventHandler 의 self instance 를 eventFetcher 에 register 하고있다. 아마도 event 데이터의 흐름이 아닐까 싶다. 그럼 eventFetcher 를 browse 해보도록 하자. 아마도 pharo 가 구동되면서 해당되는 녀석은 이미 class instance 로 등록이 되어있을거다. 그러면 browse 해도 문제는 없겠지.


아니나 다를까!!! browse 의 결과 InputEventFetcher 가 나왔다. 그럼 이녀석의 registerHandler: 메서드를 보도록 하자.

registerHandler: handler
	self eventHandlers add: handler

흠 self 로 eventHandler 를 사용하고 있다. 이놈은 어디있는 놈일까? 지금 보고있는 클래스의 인스턴스변수이다. 이 변수에 handler 를 더한다는 얘기가 되겠다. 여기서 handler 는 InputEventHandler 라는 얘기가 된다. 아 뽁잡하다.


InputEventFetcher class 의 eventLoop 이라는 메서드를 살펴보도록 하자.

eventLoop
	"Fetch pending raw events from the VM.
	 This method is run at high priority."
	| eventBuffer |

	eventBuffer := Array new: 8.
	
	[true] whileTrue: [
		| type window |
		self waitForInput.

		[self primGetNextEvent: eventBuffer.
		type := eventBuffer at: 1.
		type = EventTypeNone]
			whileFalse: [
				"Patch up the window index in case we don't get one"
				window := eventBuffer at: 8.
				(window isNil
					or: [window isZero])
					ifTrue: [eventBuffer at: 8 put: 1].	
						
				self signalEvent: eventBuffer]]


자.. 여기가 입력 대기를 기다리는 곳인가보다. 일단 내부에 eventBuffer 라는 변수가 선언되어있고 eventBuffer 는 array 로 선언된다.


XIM 입력추적을 위한 분석 :: primitive 객체

아까 위에서 generateKeyboardEvent: 의 evetBuf 가 array 라는것을 기억하는가? 그리고 generateKeyboardEvent: 의 내부에서 벼일을 꺼내서 쓴건 기억하는가? 아마...도 관련이 있지 않겠나 싶다. 일단 loop 구조를 가볍게 살펴보도록 하자.


  1. 일단 loop 를 시작할때 type 과 window 라는 2가지 변수를 선언했다.
  2. self waitForInput 이라는 메서드를 실행해서 뭔가 signal 이 올때까지 기다리는거같다.
  3. 뭔가가 오면 eventBuffer 에 primGetNextEvent: 메서드를 사용해서 뭔가를 우겨넣는다.
  4. eventBuffer 는 기본적으로 배열이다. 배열의 첫번째 칸에 숫자 1을 넣고 그걸 type 으로 대입한다.
  5. 그리고 type 과 EventTypeNone 을 비교한다. EventTypeNone 은 아까 봤던 poolDictionary 인 EventSensorConstants 딕셔너리의 class side 인 initialize 메서드 안에 들어있는 값으로서 0 이라는 상수와 동일하다. type 이 0 이 아닌경우 이후 동작을 수행하게 된다(이부분은 딱히 언급하지 않는다)


자 위의 logic 에서 또 새들어가서 살펴보자면 일단 waitForInput 이라는 메서드가 있다. 이 메서드의 내용을 보도록 하자.

waitForInput
	inputSemaphore wait.


오호.. inputSemaphore 라는게 나왔다. inputSemaphore 는 InputEventFetcher 의 인스턴스 변수이다. 자 그럼 이 변수는 어디서 초기화하는걸까? 바로 InputEventFetcher class 의 startUp 메서드에서 찾을 수 있다. startUp 메서드의 내용을 보도록 하자.

startUp
	inputSemaphore := Semaphore new.
	self primSetInputSemaphore: (Smalltalk registerExternalObject: inputSemaphore).
	inputSemaphore initSignals.
	self installEventLoop


inputSemaphore 는 Semaphore class 의 instance 가 된다. 일단 Semaphore class 는 잠시 접어두도록 하자. 여튼 primSetInputSemaphore: (primSetInputSemaphore: semaIndex) 를 통해 입력세마포어를 세팅하는데 여기서 주의할점이 있다. primSetInputSemaphore: 메서드의 내용을 보도록 하자.

primSetInputSemaphore: semaIndex
	"Set the input semaphore the VM should use for asynchronously signaling the availability of events. Primitive. Optional."
	<primitive: 93>
	^nil


오호.. 또 나왔다 "primitive". 앞쪽의 ImmX11Plugin 에서 사용하던것과는 조금 틀린 상황이다. 이번에는 그냥 번호가 붙었다. 이 번호가 의미하는것을 정확하게 알기전에 다음의 자료를 보도록 하자.


http://www.fit.vutbr.cz/study/courses/OMP/public/software/sqcdrom2/Tutorials/SqOnlineBook_%28SOB%29/englisch/sqk/sqk00051.htm


오호.. 뭔가 번호가 붙어있다. 위의 정보와 상관이 있을지도 모르겠다. 한번 93 번이 뭔지를 보자.


93 primitiveInputSemaphore


오호 93 번은 InputSemaphore 에 대한거다. 일단 Input 이라는말이 붙는걸 봐서는 관련이 제대로 있는거같다. 이걸 더 찾아보기 위해서는 좀 다른문서를 봐야할거같으니 일단 이런식으로 번호가 붙은건 이런 관게가 있다는것까지만 고려하도록 하자. 일단 대기를 위한 반복문에서 primitive 를 호출하고 있다는것을 염두에 두면 될거같다.


지금부터는 위쪽에서 알아보지 못했던 Semaphore class 에 대해 알아보기로 하자.


이 Semaphore class 의 위치는 다음과 같다.


Kernel-Processes > Semaphore


Semaphore class 의 New 메서드에는 다음과같은 내용이 있다.

new
	"Answer a new instance of Semaphore that contains no signals."

	^self basicNew initSignals


오호.. basicNew 라는게 나왔다. 이건 어디있는놈인지 좀 찾아보도록 하자. 요기에 있네?


Kernel-Classes > Behavior > instance creation(protocol) > basicNew


이 메서드의 내용을 좀 보도록 하자.

basicNew
	"Primitive. Answer an instance of the receiver (which is a class) with no 
	indexable variables. Fail if the class is indexable. Essential. See Object 
	documentation whatIsAPrimitive."

	<primitive: 70>
	self isVariable ifTrue: [ ^ self basicNew: 0 ].
	"space must be low"
	OutOfMemory signal.
	^ self basicNew  "retry if user proceeds"


그리고 initSignals 는 Semaphore class 의 인스턴스 메서드 되시겠다. 역시 내용을 구경만 해보도록 하자.

initSignals
	"Consume any excess signals the receiver may have accumulated."

	excessSignals := 0.

excessSignals 는 Semaphore 의 인스턴스 변수이다. excessSignals 변수의 값을 0으로 초기화하는거라고 보면 된다. 결과적으로 Semaphore class 의 New 메서드는 basicNew 메서드의 결과를 반환하고 인스턴스 변수를 초기화하는 코드 되시겠다.


basicNew 에서 뭔가 신경쓰이는게 또 하나 보인다. 이번에는 primitive 70 번이다. 이건 뭐하는 값일까? "70 primitiveNew" 라고 한다. 흠.. 새 primitive 를 만드는데 쓰이는거같다. 굳이 순서상으로하면 70 번 이 먼저.... 93번이 나중정도 되겠다. 70번은 모든 primitive 를 일단 생성하는데 쓰이는거라고 생각하면 맞을거같다.


이정도까지 했는데 얻어지는게 없다. 사실 이즈음에서 겨우 눈치챘지만 pharo vm image 는 아무런 잘못이 없다. 기본적으로 CogVM 에서 XIM signal 을 제대로 보내지 못하니 pharo 자체도 올바르게 처리를 못하는거라고 보면 되겠다.


XIM 입력값 교정을 위한 CogVM 의 수정

이제 pharo 에서 알아볼건 다 알아본거같다 그럼 앞으로는 CogVM 을 직접 살펴봐야할거같은데 이제부터 살펴볼건 C 소스 되시겠다. 이번부터 살펴볼 중점사항은 다음과같다.


  1. CogVM 은 -compositioninput 이라는 option 을 사용해야 XIM status 를 받아들이기 시작한다. 결과적으로 저 option 이 CogVM 내에서 어떤 역할을 하는지를 알아봐야한다.
  2. 2개의 primitive 가 내부에서 어떻게 구현되어있는지를 살펴본다. 물론 70 번과 93번. primitiveNew 와 primitiveInputSemaphore 가 그 대상이 되겠다.
  3. 아마도 XIM 에서 받은 데이터를 제대로 해석하지 못하고 vm 위에 loading 된 이미지로 보내는것같다. XIM 처리가 올바르게 되어있는지를 file log 등의 방법으로 확인하면 되곘다.
  4. 사실 CogVM 에서 ImmX11Plugin 의 역할은 현재 system 의 locale code 가 무엇인지를 확인하는정도라고밖에 보여지지 않는다. 왜냐하면 ImmX11Plugin 을 로딩하건, 로딩하지않건 동일하게 깨진코드가 들어오기 때문이다. ImmX11Plugin 이 있으면 system 의 locale 을 얻어내기는 분명히 좋지만 실질적으로 영향은 적다.


아마도 이건 내 예상이지만 CogVM 은 이미 내부에서 입력받는 모든값은 UTF-8 로 처리하고 있는듯 하다[1]. 그렇다면 CogVM 에 주어진 -encoding 의 값에 따라 XIM 으로 들어오는 값을 UTF-8 로 간주하고 처리하면 아마도 모든 문제는 해결될거라고 생각한다.


이제부터 CogVM 의 소스를 살펴보기로 한다. 물론 대상이 되는 코드는 platform sources 가 아니라 vmmaker 로 만들어진 CogVM 자체의 소스가 대상이 되겠다.


CogVM 소스의 분석 :: -compositionInput 옵션에 따른 동작내용 확인

자 CogVM 소스 디렉토리에서 다음의 명령을 실행해보자.

gentoo32 cog # grep -R compositioninput *
Binary file build/vm-display-X11/CMakeFiles/vm-display-X11.dir/root/cogvm_scripts/pharo-build/cog/platforms/unix/vm-display-X11/sqUnixX11.c.o matches
platforms/unix/vm-display-X11/sqUnixX11.c:  printf("  -compositioninput     enable overlay window for composed characters\n");
platforms/unix/vm-display-X11/sqUnixX11.c:  else if (!strcmp(arg, "-compositioninput"))
Binary file results/vm-display-X11 matches


이야.. 바로 눈에 뜨인다. "platforms/unix/vm-display-X11/sqUnixX11.c" 일단 보이니깐 살펴봐야하지 않을까? 소스코드의 해당부분을 좀 보도록 하자.(일단 두번째 걸린걸 봐야하겠다)


아 참고로 해당되는 부분을 보면 CogVM 이 아니라 일단 X11 display driver 에 관련된 문제라고 보여진다. 오히려 쉬울려나....

//line 258
static int compositionInput = 0;


//line 1558
static void initInputI18n(void)
{
  XIM im;
# if !defined(INIT_INPUT_WHEN_KEY_PRESSED)
  initInput= initInputNone;
# endif

  if (!compositionInput)
    return;

  x2sqKey= x2sqKeyPlain;
  if (XSupportsLocale() != True)
    fprintf(stderr, "XSupportsLocale() failed.\n");
  else if (!XSetLocaleModifiers(""))
    fprintf(stderr, "XSetLocaleModifiers() failed.\n");
  else if (!(im= XOpenIM(stDisplay, 0, 0, 0))) 
    fprintf(stderr, "XOpenIM() failed\n");
  else 
    {    
      static const XIMStyle pstyle[]= { XIMPreeditPosition, XIMPreeditArea, XIMPreeditNothing, XIMPreeditNone };
      static const XIMStyle sstyle[]= { XIMStatusArea, XIMStatusNothing, XIMStatusNone, 0 }; 
      XIMStyles      *styles;
      int             i, j, k;
      XVaNestedList   vlist;
# if defined(DEBUG_XIM)
      static const char const *stylename[]= { "Position", "Area", "Nothing", "None" };
      char *locale= XLocaleOfIM(im);
      fprintf(stderr, "Locale of im is %s\n", locale); 
# endif


//line 7102
static void display_parseEnvironment(void)
{
  char *ev= 0;

  if (getenv("LC_CTYPE") || getenv("LC_ALL"))
    x2sqKey= x2sqKeyInput;

  if (localeEncoding) 
    {    
      if (getenv("SQUEAK_COMPOSITIONINPUT"))
        {    
          compositionInput= 1;
          initInput= initInputI18n;
          x2sqKey= x2sqKeyCompositionInput;
        }    
    }    

  if (getenv("SQUEAK_LAZY"))            sleepWhenUnmapped= 1;
  if (getenv("SQUEAK_SPY"))             withSpy= 1;


일단 compositionInput 관련된 부분은 3곳으로 보인다. 관련된 내용들을 차근차근히 살펴보도록 하자. 일단 compositionInput option 이 켜지면 XIM 에 대한 부분은 자동적으로 활성화(?)가 되는것으로 보인다.


일단 1558 번 line 의 initInputI18n(void) 함수를 보자. 이 함수의 1677 번 Line 을 보면

x2sqKey= x2sqKeyCompositionInput;

이런 부분이 있다. (그 바로위의 setInputContextArea 는 입력되는 글자만 표시하는곳이다. 일단은 신경쓰지말자)


CogVM 에서의 인코딩판단

일단 관련해서 위쪽에 좀 살펴봐야할 부분이 있다. 위쪽의 코드를 잠시 보도록 하자 line 248 부터의 내용이다.

typedef int (*x2sqKey_t)(XKeyEvent *xevt, KeySym *symbolic);

static int x2sqKeyPlain(XKeyEvent *xevt, KeySym *symbolic);
static int x2sqKeyInput(XKeyEvent *xevt, KeySym *symbolic);
static int x2sqKeyCompositionInput(XKeyEvent *xevt, KeySym *symbolic);

static x2sqKey_t x2sqKey= x2sqKeyPlain;


오호.. x2sqKey_t 의 type 을 선언한다음 x2sqKey 의 기본값을 x2sqKeyPlain 으로 선언하고 있다. 하지만 이 앞에서 1677 번 Line 의 내용을 잠시 기억해보도록 하자. 그 내용이 작동되는 함수는 기본적으로 compositionInput option 이 있어야 동작하는걸 전제로 하고있다. 결과적으로 compositionInput option 이 설정되면 x2sqKey 는 x2sqKeyCompositionInput 으로 동작한다고 봐야한다.


x2sqKeyCompositionInput 함수는 1833 번 Line 부터 시작된다. 함수의 내용중 혹시 이상할만한게 있나 좀 살펴보도록 한다. 일단 1861 의 if 문부터 살펴보도록 하겠다. 이 부근의 if 문에서 사용되는 변수는 총 3개다.

  • localeEncoding
  • sqTextEncoding
  • uxUTF8Encoding


이중에서 sqTextEncoding 은 sqUnixMain.c 파일에서 값을 지정하며 CogVM 에서 -encoding utf-8 로 지정하는 값이 된다. option 을 별도로 주었기때문에 UTF-8 이 된다.

localeEncoding 은 system 에서 값을 가져온다. UTF-8 이 정상이다.

uxUTF8Encoding 은 kCFStringEncodingUTF8 이라는 값을 참고하게 되는데 정작 이 값이 어디서 오는지는 잘 모르겠다. 그러나 중요하지 않으니 pass 하도록 하자. (이미 현실은 시궁창이다)


XIM 으로 들어오는 입력값에 대한 추적 및 정상동작을 위한 수정

현재 이 소스에서는 두가지의 define 값을 사용할 수 있다. 사실은 cmake level 에서 처리되어야 할거라고 생각되지만 (-D 등의 option으로) 나는 cmake 를 잘 모르니 일단 소스코드 내에서 처리하기로 했다. sqUnixX11.c 파일의 상단에 다음과같은 부분을 추가해준다.

#define X_HAVE_UTF8_STRING
#define DEBUG_XIM


사실 이런저런부분을 조사했지만 다른건 다 필요없었다. 중요한건 1961 번째 줄즈음에 있는 recordPendingKeys(); 부분이 vm 내의 image 안쪽으로 입력되는 값을 전달하는 경우가 되시겠다. 그럼 recordPendingKeys 함수의 내용을 좀 보도록 하자.

static int recordPendingKeys(void)
{
  if (inputCount > 0) 
    {    
      int i= iebOut - iebIn;

      for (i= (i > 0 ? i : IEB_SIZE + i) / 4; i > 0; -- i)
        {
# if defined(DEBUG_XIM)
          fprintf(stderr, "%3d pending key %2d=0x%02x\n", inputCount, i, *pendingKey);
# endif
          recordKeyboardEvent(*pendingKey, EventKeyDown, modifierState, 0);
          recordKeyboardEvent(*pendingKey, EventKeyChar, modifierState, 0);
          recordKeystroke(*pendingKey);  /* DEPRECATED */
          ++pendingKey;
          if (--inputCount == 0) break;
        }
      return 1;
    }    
  /* inputBuf is allocated by lookupKeys */
  if (inputBuf != inputString)
    {    
      free(inputBuf);
      inputBuf= inputString;
    }    
  return 0;
}


자 간단한 함수 되시겠다. 그럼 이 함수의 내용을 일단 필요한대로 수정해놓고 얘기를 시작하도록 하자.

static int recordPendingKeys(void)
{
  if (inputCount > 0)
    {
      int i= iebOut - iebIn;

# if defined(X_HAVE_UTF8_STRING)
      int pendingKey_ucs4[IEB_SIZE + i];
      char * dummy_ucs4=(char *)pendingKey_ucs4;
      char * from_utf8=pendingKey;

      iconv_t conv;
      size_t nconv;
      //size_t insize=strlen(from_utf8);
      size_t insize=inputCount;
      size_t avail=sizeof(pendingKey_ucs4);

      memset(pendingKey_ucs4,0,avail);

      conv = iconv_open ("UCS-4LE","UTF-8");
      nconv = iconv (conv, &from_utf8, &insize, &dummy_ucs4, &avail);
      iconv_close(conv);

      for (i = 0; pendingKey_ucs4[i]; i++)
        {
# if defined(DEBUG_XIM)
          fprintf(stderr, "%3d pending key %2d=0x%02x\n", inputCount, i, pendingKey_ucs4[i]);
# endif
          recordKeyboardEvent(pendingKey_ucs4[i], EventKeyDown, modifierState, pendingKey_ucs4[i]);
          recordKeyboardEvent(pendingKey_ucs4[i], EventKeyChar, modifierState, pendingKey_ucs4[i]);
          recordKeystroke(pendingKey_ucs4[i]);  /* DEPRECATED */
          if (--inputCount == 0) break;
        }
      inputCount = 0;
# endif


# if !defined(X_HAVE_UTF8_STRING)
      for (i= (i > 0 ? i : IEB_SIZE + i) / 4; i > 0; -- i)
        {
# if defined(DEBUG_XIM)
          fprintf(stderr, "%3d pending key %2d=0x%02x\n", inputCount, i, *pendingKey);
# endif
          recordKeyboardEvent(*pendingKey, EventKeyDown, modifierState, 0);
          recordKeyboardEvent(*pendingKey, EventKeyChar, modifierState, 0);
          recordKeystroke(*pendingKey);  /* DEPRECATED */
          ++pendingKey;
          if (--inputCount == 0) break;
        }
# endif
      return 1;
    }
  /* inputBuf is allocated by lookupKeys */
  if (inputBuf != inputString)
    {
      free(inputBuf);
      inputBuf= inputString;
    }
  return 0;
}


중간에 코드들이 추가되었다 사실 기존에 안되고있던.. 그리고 굳이 한개의 글자가 3개로 나뉘어서 들어간건 UTF-8 로 조합된 결과를 buffer 로 받아놓고 (이경우 buffer 는 pendingKey 라는놈이 되겠다) 그걸 일반 문자처럼 loop 돌려가며 처리해서 그렇다. 위의 내용을 잘 보면 알 수 있을지 모르겠지만 사실 image 안쪽으로 키값을 전송하는 함수는 recordKeyboardEvent 가 된다. 이걸 보면 알 수 있곘지만.. keydown, keyup, keystroke 를 다 같은값을 보낸다. 참 쿨하다....


여튼간에 recordKeyboardEvent 의 선언을 찾아봐야 이후의 설명을 할 수 있다. grep 으로 찾아보도록 하자. 다음과같은 결과을 얻을 수 있으면 성공!

cog/platforms/unix/vm/sqUnixEvent.c:static void recordKeyboardEvent(int keyCode, int pressCode, int modifiers, int ucs4)


흐음 sqUnixEvent.c 라는 파일에 해당되는 내용이 있는거같다. 그럼 해당되는 함수의 구현을 좀 봐야할 필요는 있지않을까?

static void recordKeyboardEvent(int keyCode, int pressCode, int modifiers, int ucs4)
{
  sqKeyboardEvent *evt= allocateKeyboardEvent();
  if (keyCode < 0) keyCode= 0;
  evt->charCode= keyCode;
  evt->pressCode= pressCode;
  evt->modifiers= modifiers;
  evt->utf32Code= ucs4;
  evt->reserved1=
    evt->windowIndex= 0;
  signalInputEvent();
#if DEBUG_KEYBOARD_EVENTS
  printf("EVENT: key");
  switch (pressCode)
    {
    case EventKeyDown: printf(" down "); break;
    case EventKeyChar: printf(" char "); break;
    case EventKeyUp:   printf(" up   "); break;
    default:           printf(" ***UNKNOWN***"); break;
    }
  printModifiers(modifiers);
  printKey(keyCode);
  printf(" ucs4 %d\n", ucs4);
#endif
}


흠.. 다른건 그냥 봐도 상관없을거같은데 내부적으로 utf32Code 를 ucs4 로 취급하는거에 좀 주의해야할 필요가 있다. 오호.. UTF-8 로 보내는게 아닌거같다. 일단 보낼때는 UCS-4 를 써야하나보다. 위쪽의 수정된 코드중에 일부분을 보도록 하자.

conv = iconv_open ("UCS-4LE","UTF-8");

뭔가 자주 안보이는게 보이지만 무시하자. 여기서 주의해야할 부분은 "UCS-4LE" 가 되겠다. 왜 ucs4 가 아닌가? 그건 endiean 때문이다. LE 는 little endian 의 약어로서 이거 안맞춰주면 잘못된 code 가 들어간다고 하면서 CogVM 이 에러를 뿜는다. (물론 처리따위도 하지않는다)


추가된 로직을 별도로 보기전에 sqUnixX11.c 파일 상단에 넣어줘야 하는 구문이 하나 더 있다.

#include <iconv.h>


UTF-8 을 UCS-4(little endian) 으로 변환하기 위해 iconv 를 사용하도록 한다. 사실은 iconv 에 의존하지말고 바로 bit shift 연산등을 통해서 변환하는 코드를 넣어야 하겠지만.. 귀찮다. 그런건 CogVM 의 개발자에게 맡기도록 하자. iconv 함수를 사용하기위해 위의 처리를 해주어야 한다.


자.. 이제는 추가된 로직만을 따로 분리해서 보도록 하겠다.

# if defined(X_HAVE_UTF8_STRING)
      int pendingKey_ucs4[IEB_SIZE + i];
      char * dummy_ucs4=(char *)pendingKey_ucs4;
      char * from_utf8=pendingKey;

      iconv_t conv;
      size_t nconv;
      //size_t insize=strlen(from_utf8);
      size_t insize=inputCount;
      size_t avail=sizeof(pendingKey_ucs4);

      memset(pendingKey_ucs4,0,avail);

      conv = iconv_open ("UCS-4LE","UTF-8");
      nconv = iconv (conv, &from_utf8, &insize, &dummy_ucs4, &avail);
      iconv_close(conv);

      for (i = 0; pendingKey_ucs4[i]; i++)
        {
# if defined(DEBUG_XIM)
          fprintf(stderr, "%3d pending key %2d=0x%02x\n", inputCount, i, pendingKey_ucs4[i]);
# endif
          recordKeyboardEvent(pendingKey_ucs4[i], EventKeyDown, modifierState, pendingKey_ucs4[i]);
          recordKeyboardEvent(pendingKey_ucs4[i], EventKeyChar, modifierState, pendingKey_ucs4[i]);
          recordKeystroke(pendingKey_ucs4[i]);  /* DEPRECATED */
          if (--inputCount == 0) break;
        }
      inputCount = 0;
# endif


간단하게 설명해보겠다. 기존의 logic 은 unicode 를 기준으로 loop 를 돌지 않는다. 그렇기때문에 여기서는 들어온 pendingKey 를 UCS4 로 변환하고 변환된 문자열을 기준으로 loop 를 돌면서 recordKeyboardEvent 에 값을 집어넣는다. inputCount 를 0 으로 강제로 바꿈으로서 buffer 가 끝났을음 알린다. 해당되는 함수 전문을 보면 알겠지만 "# if !defined(X_HAVE_UTF8_STRING)" 구문을 통해 이전의 code 는 살려놓도록 했다. 어떤식으로 처리했는지는 소스코드를 참고하도록 하자.


XIM 입력값에 따른 에러 처리 :: 키 입력값이 제어코드 및 ascii 영역에 포함되는 경우

사실 지금 이것도 버그가 좀 있다. 한글 입력 상태에서 느낌표등 ascii 코드를 처리하는 분에 오류등이 있다. 어디에서 관련된 처리를 하는지 좀 찾아보자. 일단 debug 로 진행한다고 했을때 경우에 따른 결과를 살펴보자. 아래의 코드를 참고해서 디버깅을 해보도록 하자.

static void handleEvent(XEvent *evt)

line 3647....

        KeySym symbolic;
        int keyCode= x2sqKey(&evt->xkey, &symbolic);
        int ucs4= xkeysym2ucs4(symbolic);
        //DCONV_FPRINTF(stderr, "symbolic, keyCode, ucs4: %x, %d, %d\n", symbolic, keyCode, ucs4);
        fprintf(stderr, "symbolic, keyCode, ucs4: %x, %d, %d\n", symbolic, keyCode, ucs4);
        //DCONV_FPRINTF(stderr, "pressed, buffer: %d, %x\n", multi_key_pressed, multi_key_buffer);
        fprintf(stderr, "pressed, buffer: %d, %x\n", multi_key_pressed, multi_key_buffer);


case 1 : XIM kor "1"

symbolic, keyCode, ucs4: 8184150, 49, 0
pressed, buffer: 0, 0


case 2 : XIM eng "1"

symbolic, keyCode, ucs4: 31, 49, 49
pressed, buffer: 0, 0


자.. 영문과 한글의 경우가 틀리다. 영문의 경우에는 symbolic 의 값이 정상적으로 keyCode 에 대응하는 16진수값을 뿌려주지만 한글의 경우에는 symbolic 값이 이상하게 들어감으로서 xkeysym2ucs4 의 결과값이 원하는대로 반환되지 않고 0 이 반환된다. 위에서 언급한 코드 부분을 좀 수정을 하도록 한다.

    case KeyPress:
      noteEventState(evt->xkey);
      {
        KeySym symbolic;
        int keyCode= x2sqKey(&evt->xkey, &symbolic);
        int ucs4= 0;

        if (keyCode > 32 && keyCode < 128)
                ucs4= keyCode;
        else
                ucs4= xkeysym2ucs4(symbolic);

        DCONV_FPRINTF(stderr, "symbolic, keyCode, ucs4: %x, %d, %d\n", symbolic, keyCode, ucs4);
        DCONV_FPRINTF(stderr, "pressed, buffer: %d, %x\n", multi_key_pressed, multi_key_buffer);

keyCode 가 일반적인 문자인 128 이하인경우에는 ucs4 에 keyCode 를 넣고 128 이상인 경우에는 xkeysym2ucs4 를 이용해서 값을 넣게했다. 이야.. 이번에는 숫자에 일반문자까지 잘 들어가고 있다.


자 이제 대략 작업이 된거같다. 아까전에 위쪽에 추가했던 "#define DEBUG_XIM" 부분을 제거하고 다시 CogVM 을 compile 한다. 이제는 단축키부터 모든게 잘된다. 이 vm 을 사용하면 뭘 해도 문제따위는 없으시겠다. 다만 야매 patch 이므로 반드시 주의하도록 한다. 다 끝내게되면 아래와같은 동작을 확인할 수 있을것이다

Pharo XIM 06.png


사용된 파일들

File:Cogvm resource.7z

파일안의 내용은 다음과같다.

  • 00_for_ImmX11Plugin - ImmX11Plugin compile 에 사용된 파일들이 들어있다.
  • 01_for_XIMpatch_to_X11driver - XIM 입력값 patch 를 위해 작업된 파일이 있다.
  • 02_binary - compile 된 linux 버전의 CogVM 이 들어있다. 단 pharo image 는 포함되어있지 않으니 주의할것


참고자료

참고자료::web document


참고자료::pdf document


thanks to

  • 중간중간 좋은개념 알려주신 승범님 감사합니다...
  • 뜬금없는 요청에도 흔쾌히 답장을 주신 Marcus Denker <marcus.denker@inria.fr> 님 감사합니다...
  • 그외에 이름을 알리기 애매한 조력자(라고쓰고 지인이라고 읽는)님께도 감사드립니다. 아마 당신이 없었으면 patch 못했을겁니다..^.^;


Notes

  1. 사실 분석을 하면서 알게된 거지만 UCS-4 를 사용하는듯하다. little endian 을 사용하기때문에 정확히는 UCS-4LE 를 스는게 아닐까..라고 짐작하고 있다.