顯示具有 opensource project 標籤的文章。 顯示所有文章
顯示具有 opensource project 標籤的文章。 顯示所有文章

2012年11月14日 星期三

[懷舊] jmcce 1.5 (0) - 從死亡到重生

jmcce 是一套中文終端機, linux 誕生之後的幾年, 在中文還要修修補補才能使用的年代, 有好幾套中文終端機, yact, bcs16, chdrv, jmce, zhcon ... 現在己經不太流行了。
ref: http://www.linux.org.tw/CLDP/OLD/Chinese-HOWTO-4.html

若你也經歷過那時期, 應該聽過 jmcce, 我使用過的 linux 中文終端機有 yact, chdrv, jmce。注意 jmce 和 jmcce 是不同的。 jmcce 則是我最後用過的中文終端機, 也是我覺得最好用的一個。最後一版應該是 jmcce 1.4, 那標題的 jmcce 1.5 是那裡來的, 當然是我自己修改的 XD

根據這篇: http://lists.linux.org.tw/pipermail/cle-devel/2002-May/002331.html jmcce 1.4 大約是在 2001 左右出現的, 那時候我還向開發者回報一些問題, 很快就得到修正的版本, 那時候的我還無法參與修改; 十年後 - 2012 年, 我把 jmcce 1.4 rc2 重新修改, 原有的 autobuild tool script 已經過時, 無法在新版本的 autotool 使用, 這東西實在煩人, 我只略懂, 不想花時間在上面, 我改為人工手寫 makefile, 至少可以 compile 了, 當然也很容易 cross compile。印象中 jmcce 有 1.4 正式版本, 不過我只找到 rc2 的版本, 我是從這版本改起。

jmcce 有些程式碼似乎是從 yact 來的, 我看了一下 yact source code, 裡頭有些 code 很雷同, yact 很偉大阿!

對於 opensource 的開發方式並不陌生, 不過我從來沒想過我也可以參與 opensource 開發。jmcce 成為我的第一次, 儘管她有點過時了, 不過讓死掉的專案復活, 爽度暴增。

為什麼挑上 jmcce? 繪圖模式下的終端機有, fbcon, fbterm ... fbcon 是由 linux kernel module 提供的, 相容性最好, 不過我不知道能不能顯示多國語言。

而因為我只喜歡在需要的時候才進入繪圖模式, 使用 svgalib 撰寫的 jmcce 符合這原則, 不過畢竟是古老的系統, 在 utf8 當道的現今, 她還是只能使用 big5。而使用 linux framebuffer 的終端機並不受我青睞, 但在非 x86 環境下, 就只能使用 linux framebuffer。

fbterm 似乎很先進, 有很多功能。jmcce 要做到 fbterm 還有很多程式碼要寫, 但是 jmcce 有著另外的優勢, 就是很多輸入法, 這就是 fbterm 沒有的了。fbterm 有用到 libx86, 這是幹嘛用的? 很神奇, 下一篇再講。

有一個 v86d 就是用 libx86 寫的。我的 fb 測試需要用到它。

NAME
       v86d - daemon to run x86 code in an emulated environment

SYNOPSIS
    v86d

DESCRIPTION
    v86d  provides  a  backend for kernel drivers that need to execute x86 BIOS code. The
    code is executed in a controlled environment and the results are passed back  to  the
    kernel via the netlink interface.

    v86d should NOT be run by the user, the kernel will call it, when it is needed.

AUTHOR
    v86d is written by Michal Januszewski.

    This  manual page was written by Evgeni Golov , for the Debian
    project (but may be used by others).

module uvesafb.ko 會需要 v86d, 真奇怪, kernel module 竟然需要 user mode daemon??

linux fb test:

測試 fb 是否有驅動起來
fbset -s
open /dev/fb0: No such file or directory

沒看到這字串就是有啟動 framebuffer, 這時可以看看解析度是多少。

在我的 eeepc 901 上測試 linux fb 時, 需要移除 i915 module, 並且安裝 v86d, 再 modprobe uvesafb, 然後使用 fbset 設定解析度為 640X480X256 color (jmcce 在這模式下才有最正常的畫面)。i915 gpu module 除了補強 x driver 外, 似乎還有著 framebuffer 的功能。

fbset -xres 640 -yres 480 -vxres 640 -vyres 480 -depth 8

也可以單純改顏色為 8bit, jmcce 畫面只會佔用 640x480
fbset -depth 8

若是直接使用 grub vga=769 這參數, 似乎無法使用 fbset 改變解析度。

提供另外的 fb command 作為參考。

hwinfo --framebuffer | grep Mode

linux svgalib test:

在 debian 關閉 linux framebuffer module 才能正常 (使用 uvesafb 則可以執行)。不保證能在 debian 以外的環境能正常執行。測試時最好不要開啟 framebuffer, 我是把 framebuffer module 刪除。不過在載入 uvesafb 時, svgalib 版本還是可以執行, 但是顏色有點問題。有問題就別掙扎了, 改用 fb 模式會愉快些, 這不是容易搞定的問題。

我打算用 c++ compiler, 就算不用 c++ 物件導向的功能, 也可以享受 c++ 標準程式庫帶來的便利性, 是的, jmcce 1.5 已經是 c++ 程式了, .c 結尾的程式名稱其實是 c++ 程式, 我懶得改了。爽快的使用 c++ 帶來的語言特性吧!

會重新開發這個程式只是想懷舊一下 svgalib 程式, 意外發現還看得懂 jmcce 的程式碼, 就順手改了起來, 原本以為只要花點時間改 makefile 會動就好, 沒想到到會動花了我 3 天時間, 更糟糕的是, 我還愈做愈有興趣 xD。看到 jmcce 成功秀出中文時, 那時的興奮之情到現在都還無法忘記。

20121009 ~ 20121030 這段期間我總共修改了:
  • build system: manual makefile
  • pseudo-terminal - use openpty()
  • remove old chewing chinese input method
  • add libchewing support
  • compile by g++4
  • remove asm function (asm_routine.S)
  • limit support utf8 chinese
  • compile linux fb without svgalib
  • use svgalib gl_xxx function, implement scroll function, display speed faster than vga_xxx, resolution fixed to 640x480x256 (vga_xxx is 640x480x16)
其實只要修改幾個地方就可以復活, 不過我加入 gl_xxx 系列的繪圖函式, 修改了捲動部份, 花了一點時間, 原本的 vga_xxx 沒有繼續修正, 不過也不需要了, gl_xxx 快很多。而組合語言的部份我拿掉了, 這會和 1.9.x 的 svgalib 相衝。

libchewing 已經改為使用 utf8 編碼, 我還特地將 utf8 -> big5, 有些字無法轉換成 big5, 有點蠢阿!

目前能使用 big5 和有限的 utf8 中文編碼, 有限度的支援中文 utf8, 為什麼有限度, 因為我是將 utf8 編碼轉成 big5 在 jmcce 上畫出, 一定會有 utf8 無法轉成 big5 的中文字, 要整個改版實在困難, 先將就著用吧!按下 ctrl+alt+8 直接切換 big5/utf8, 痛快。

我想如果沒支援 linux framebuffer 和 utf8, 這個中文終端機大概沒人會想用, 畢竟 x 環境和早期相比, 已經進步不少, 沒必要使用 console 中文終端機了。其實我自己也不想用; 現在頂多拿來上 ptt bbs, 已經勉強可以用酷音輸入; 在 keymap 的這邊似乎還有點問題, 先到這邊就好了, 我花了很多時間在找資料, 不過這部份資料實在不好找, 期待有強者改善這部份。暫時就這樣, 在改下去可能會是無底洞。

key function:
ctrl+alt+3 注音
ctrl+alt+7 酷音
ctrl+alt+8 utf8/big5 toggle

這只是個開始, 離能用還遠得很, 我也不見得會繼續完善她, 我的重心還是擺在 simple os 開發, 希望有其他朋友能一起參與 jmcce。









原本的 svgalib vga_xx function, 捲動速度實在很慢。 解析度: 640x480x16 color。

改用 svgalib gl_xx function, 捲動速度快了不少, 還我本速。 解析度: 640x480x256 color。

捲動處理:
console.c
scroll_down
scroll_up

酷音輸入法 api 使用方式:

todo list:
  • use linux framebuffer more mode, ex: 640X480X24bit color, 1024X768X32bit color
  • fixed linux keymap
  • real utf8 support
  • freetype2 supoort 
  • tune libchew input method

我希望能完成上述功能, 不過再來的進度不會這麼快, 慢慢來吧!

source code:
https://github.com/descent/jmcce

apt-get install libchewing3-dev libsvga1-dev
cd jmcce/src
make vgatest
make 
./jmcce

depend library:
libchewing
svgalib
ncurses

有興趣的朋友自己玩玩看, 有編譯上的問題可以問我。執行 jmcce 前可先執行 vgatest, 看看能否正常顯示 640X480X256 的模式。

lfb branch 有個只 support fb 的版本, 不使用 svgalib。

要是你看到 "descent modify jmcce 1.4 rc2" 覺得很不爽, grep VERSION_STRING 改成你喜歡的字串。

#define VERSION_STRING "descent modify jmcce 1.4 rc2 80x24"


2012年11月11日 星期日

[懷舊] jmcce 1.5 (1) - in apple ibook/ubuntu (powerpc)

這次要在 ibook/ubuntu (cpu: powerpc) 上測試 jmcce。

不過先來聊聊 fbterm 神奇的 vesa dirver。fbterm 除了使用 linux framebuffer 還提供了 vesa driver, 這是怎麼做的? 我很好奇, 原來是使用 libx86, 進而呼叫 bios 0x10 來使用 bios vesa function, 參考 vesadev.cpp L 48 ~ 61:

210 void VesaDev::printModes()

從這裡開始追起, linux 還有這樣的 library 可以用, 真是令我驚訝。不過這不是我想要的方式, 我想要的方式是直接操作 vga register, 不使用 bios call。這個 library 神奇的地方是: linux 處於保護模式下, 無法使用 bios call, 可是這 library 卻可以提供呼叫 bios 的能力, 真是厲害。

vesadev.cpp
1 /*
2 * Copyright © 2008-2010 dragchan <zgchan317@gmail.com>
3 * This file is part of FbTerm.
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 *
19 */

20
21 #include "config.h"
22 #ifdef ENABLE_VESA
23
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <unistd.h>
27 #include <string.h>
28 #include <fcntl.h>
29 #include <sys/io.h>
30 #include <sys/mman.h>
31 extern "C" {
32 #include <libx86.h>
33 }
34 #include "vesadev.h"
35 #include "vbe.h"
36 #include "fbterm.h"
37
38 #define MAX_VIDEO_MEM (16 * 1024 * 1024)
39 #define MIN(a,b) ((a) < (b) ? (a) : (b))
40
41 static s16 cur_mode;
42 static vbe_mode_info_block mode_info;
43 static LRMI_regs r;
44 static u32 scanline_width, scanline_height;
45
46 extern u32 effective_uid;
47
48 static bool call_bios()
49 {
50 bool success = true;
51 u16 fun = r.eax;
53 if (!LRMI_int(0x10, &r)) success = false;
54 if ((fun >> 8) == 0x4f && (r.eax & 0xffff) != 0x4f) success = false;
56 if (!success) {
57 fprintf(stderr, "call VESA function 0x%x failure!\n", fun);
58 }
60 return success;
61 }
62
63 static void save_restore_state(bool save)
64 {
65 static void *state_buf;
66 static u32 state_size;
67 static bool saved = false;
68
69 if (save) {
70 if (saved) return;
71
72 r.eax = VBE_FUN_SAVE_RESTORE_STATE;
73 r.ecx = 0xf; /* all states */
74 r.edx = 0; /* get buffer size */
75
76 if (!call_bios()) return;
77
78 state_size = (r.ebx & 0xffff) * 64;
79 state_buf = malloc(state_size);
80
81 void *buf = LRMI_alloc_real(state_size);
82
83 r.eax = VBE_FUN_SAVE_RESTORE_STATE;
84 r.ecx = 0xf; /* all states */
85 r.edx = 1; /* save state */
86 r.es = (long)buf >> 4;
87 r.ebx = (long)buf & 0xf;
88
89 if (call_bios()) {
90 saved = true;
91 memcpy(state_buf, buf, state_size);
92 }
93
94 LRMI_free_real(buf);
95 } else if (saved) {
96 void *buf = LRMI_alloc_real(state_size);
97 memcpy(buf, state_buf, state_size);
98
99 r.eax = VBE_FUN_SAVE_RESTORE_STATE;
100 r.ecx = 0xf; /* all states */
101 r.edx = 2; /* restore state */
102 r.es = (long)buf >> 4;
103 r.ebx = (long)buf & 0xf;
104
105 call_bios();
106 LRMI_free_real(buf);
107 }
108 }
109
110 static void save_restore_mode(bool save)
111 {
112 static s16 mode = -1;
113
114 if (save) {
115 r.eax = VBE_FUN_GET_CURRENT_MODE;
116 if (call_bios()) mode = r.ebx;
117 } else if (mode != -1) {
118 r.eax = VBE_FUN_SET_MODE;
119 r.ebx = mode;
120 call_bios();
121 }
122 }
123
124 // input: mode 0 for auto-detect
125 // return: -1 for failure
126 static s16 get_mode(s16 mode, bool only_print)
127 {
128 seteuid(0);
129 bool ret = (!LRMI_init() || ioperm(0, 1024, 1) || iopl(3));
130 seteuid(getuid());
131
132 if (ret) {
133 fprintf(stderr, "Using VESA requires root privilege\n");
134 return -1;
135 }
136
137 vbe_info_block *info = (vbe_info_block *)LRMI_alloc_real(sizeof(*info));
138
139 r.eax = VBE_FUN_GET_INFO;
140 r.es = (long)info >> 4;
141 memcpy(info->vbe_signature, "VBE2", 4);
142
143 if (!call_bios() || strncmp(info->vbe_signature, "VESA", 4) != 0) {
144 fprintf(stderr, "No VESA bios\n");
145 LRMI_free_real(info);
146 return -1;
147 }
148
149 s16 *mode_list = (s16*)(info->video_mode_list_seg * 16 + info->video_mode_list_off);
150 vbe_mode_info_block *minfo = (vbe_mode_info_block *)LRMI_alloc_real(sizeof(*minfo));
151 u32 xres_tmp, yres_tmp, bpp_tmp;
152 s16 mode_tmp = -1;
153 bool valid_mode = false;
154
155 for (; *mode_list != -1; mode_list++) {
156 r.eax = VBE_FUN_GET_MODE_INFO;
157 r.ecx = *mode_list;
158 r.es = (long)minfo >> 4;
159
160 if (!call_bios()) continue;
161
162 if (!(minfo->mode_attributes & VBE_ATTR_MODE_SUPPORTED)
163 || !(minfo->mode_attributes & VBE_ATTR_COLOR)
164 || !(minfo->mode_attributes & VBE_ATTR_GRAPHICS)
165 || !(minfo->mode_attributes & VBE_ATTR_LINEAR)) continue;
166
167 switch (minfo->bits_per_pixel) {
168 case 8:
169 if (minfo->memory_model != VBE_MODEL_PACKED) continue;
170 break;
171 case 15:
172 case 16:
173 case 32:
174 if (minfo->memory_model != VBE_MODEL_RGB) continue;
175 break;
176 default:
177 continue;
178 }
179
180 valid_mode = true;
181
182 if (only_print) {
183 printf("[%d]: %dx%d-%dbpp\n", *mode_list,
184 minfo->x_resolution, minfo->y_resolution, minfo->bits_per_pixel);
185 } else {
186 if (mode) {
187 if (mode == *mode_list) return mode;
188 } else {
189 if ((mode_tmp == -1)
190 || (minfo->x_resolution > xres_tmp && minfo->y_resolution > yres_tmp)
191 || (minfo->x_resolution == xres_tmp && minfo->y_resolution == yres_tmp && minfo->bits_per_pixel > bpp_tmp)) {
192
193 mode_tmp = *mode_list;
194 xres_tmp = minfo->x_resolution;
195 yres_tmp = minfo->y_resolution;
196 bpp_tmp = minfo->bits_per_pixel;
197 }
198 }
199 }
200 }
201
202 if (!valid_mode) fprintf(stderr, "can't find a valid VESA mode for your video card\n");
203 else if (!only_print && mode_tmp == -1) fprintf(stderr, "%d isn't a valid VESA mode for your video card\n", mode);
204
205 LRMI_free_real(info);
206 LRMI_free_real(minfo);
207 return mode_tmp;
208 }
209
210 void VesaDev::printModes()
211 {
212 get_mode(0, true);
213 }
214
215 VesaDev *VesaDev::initVesaDev(s16 mode)
216 {
217 cur_mode = get_mode(mode, false);
218 if (cur_mode == -1) return 0;
219
220 void *minfo = LRMI_alloc_real(sizeof(mode_info));
221
222 r.eax = VBE_FUN_GET_MODE_INFO;
223 r.ecx = cur_mode;
224 r.es = (long)minfo >> 4;
225 call_bios();
226
227 memcpy(&mode_info, minfo, sizeof(mode_info));
228 LRMI_free_real(minfo);
229
230 cur_mode |= 0x4000; // use linear frame buffer mode
231 return new VesaDev();
232 }
233
234 VesaDev::VesaDev()
235 {
236 mWidth = mode_info.x_resolution;
237 mHeight = mode_info.y_resolution;
238 mBitsPerPixel = mode_info.bits_per_pixel;
239
240 scanline_width = mWidth;
241 scanline_height = mHeight;
242 }
243
244 VesaDev::~VesaDev()
245 {
246 if (mVMemBase) munmap(mVMemBase, mBytesPerLine * scanline_height);
247 }
248
249 const s8 *VesaDev::drvId()
250 {
251 return "VESA";
252 }
253
254 void VesaDev::switchVc(bool enter)
255 {
256 if (enter) {
257 static bool inited = false;
258
259 if (!inited) {
260 save_restore_mode(true);
261 save_restore_state(true);
262 }
263
264 r.eax = VBE_FUN_SET_MODE;
265 r.ebx = cur_mode;
266 call_bios();
267
268 r.eax = VBE_FUN_LOGICAL_SCANLINE;
269 r.ebx = 0; // set scan line length in pixels
270 r.ecx = scanline_width;
271 call_bios();
272
273 if (!inited) {
274 inited = true;
275
276 r.eax = VBE_FUN_LOGICAL_SCANLINE;
277 r.ebx = 1; // get scan line length
278 call_bios(); // return ebx: scan line length in bytes
279 // ecx: scan line length in pixels
280 // edx: maximum scan lines
281
282 mBytesPerLine = r.ebx;
283 initScrollType();
284
285 seteuid(0);
286 s32 fd = open("/dev/mem", O_RDWR);
287 seteuid(getuid());
288
289 mVMemBase = (u8 *)mmap(0, mBytesPerLine * scanline_height, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mode_info.phys_base_ptr);
290 close(fd);
291 }
292 } else {
293 save_restore_mode(false);
294 save_restore_state(false);
295 }
296
297 Screen::switchVc(enter);
298 }
299
300 void VesaDev::initScrollType()
301 {
302 r.eax = VBE_FUN_DISPLAY_START;
303 r.ebx = 0; // set display start
304 r.ecx = 0; // x = 0
305 r.edx = 0; // y = 0
306 if (!call_bios()) return;
307
308 if (mRotateType == Rotate0 || mRotateType == Rotate180) {
309 r.eax = VBE_FUN_LOGICAL_SCANLINE;
310 r.ebx = 1; // get scan line length
311 call_bios();
312
313 u32 max_height = MIN(r.edx, MAX_VIDEO_MEM / mBytesPerLine);
314 if (max_height <= mHeight) return;
315
316 mScrollType = YPan;
317 mOffsetMax = max_height - mHeight;
318 scanline_height = max_height;
319 } else {
320 // mWidth/mHeight has been swapped
321 u32 width = mHeight, height = mWidth;
322
323 r.eax = VBE_FUN_LOGICAL_SCANLINE;
324 r.ebx = 3; // get maximum scan line length
325 if (!call_bios()) return;
326
327 u32 max_width = r.ecx;
328
329 r.eax = VBE_FUN_LOGICAL_SCANLINE;
330 r.ebx = 0; // set scan line length in pixels
331 call_bios();
332
333 r.eax = VBE_FUN_LOGICAL_SCANLINE;
334 r.ebx = 1; // get scan line length
335 call_bios();
336
337 if (r.ecx <= width || r.edx < height) return;
338
339 scanline_width = max_width;
340 mScrollType = XPan;
341 mBytesPerLine = r.ebx;
342 mOffsetMax = r.ecx - width;
343 }
344 }
345
346 void VesaDev::setupOffset()
347 {
348 if (mScrollType != YPan && mScrollType != XPan) return;
349
350 r.eax = VBE_FUN_DISPLAY_START;
351 r.ebx = 0; // set display start
352 if (mScrollType == YPan) {
353 r.ecx = 0;
354 r.edx = mOffsetCur;
355 } else {
356 r.ecx = mOffsetCur;
357 r.edx = 0;
358 }
359 call_bios();
360 }
361
362 void VesaDev::setupPalette(bool restore)
363 {
364 if (mode_info.memory_model != VBE_MODEL_PACKED || restore) return;
365
366 vbe_palette_entry *buf = (vbe_palette_entry *)LRMI_alloc_real(sizeof(*buf) * NR_COLORS);
367
368 for (u32 i = 0; i < NR_COLORS; i++) {
369 buf[i].red = mPalette[i].red >> 2;
370 buf[i].green = mPalette[i].green >> 2;
371 buf[i].blue = mPalette[i].blue >> 2;
372 }
373
374 r.eax = VBE_FUN_PALETTE_DATA;
375 r.ebx = 0; // set palette data
376 r.ecx = NR_COLORS; // number of palette entry
377 r.edx = 0; // first palette entry
378 r.es = (long)buf >> 4;
379 call_bios();
380
381 LRMI_free_real(buf);
382 }
383 #endif

powerpc ubuntu 平台沒有 libx86 可裝, 無法使用 uvesa, 只能用其他的 fb 功能, 我是拿掉 radeon module, 修正一些 compile issue 後, 可以在 ibook/ubuntu 上使用。

下方影片是 jmcce 在 ibook (cpu: powerpc) ubuntu 10.04 上的測試畫面, 純粹使用 linux framebuffer, 由於 jmcce 只支援 fb 256 色, 所以需要使用 fbset -depth 8 設定為 256 色, 解析度則因為無法調整為 640X480, 維持原來的 1024X768, 所以畫面看起來就變成這樣子了。捲動效果還是不快。



非 x86/linux 無法使用 svgalib, 所以只能單純的使用 framebuffer, 看來執行得還算可以。