Last active
June 27, 2022 19:29
-
-
Save dixyes/cd945d8a195889de32cb524254d90d33 to your computer and use it in GitHub Desktop.
use luajit, ncurses(w) to create a command line ui
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/home/dixyes/awsl/pl/luajit | |
--[[ | |
gist: https://gist.github.com/dixyes/cd945d8a195889de32cb524254d90d33 | |
First to say : ffi niubi! | |
This file is ncursesw command line ui written in lua, using luajit and some C things. | |
Note: pits here: | |
1. variables created by ffi.new("sometype") may be gced at anytime (maybe use ffi.typeof() to create type can resolve this.); | |
workaround: use ffi.C.malloc instead | |
2. struct index may be not reliable: for example a structure struct sStruct{u32 lFirst; u64 llSecond;}; when use instSStruct[0].llSecond, it will use area from llSecond's half to out of bound 4 bytes as llSecond. | |
workaround: use structs only one type of members | |
Author: Yun Dou (dixyes) <[email protected]> | |
License: MIT license | |
In short (explain only): you can use it as you like, but ABSOLUTELY NO WARRANTY! | |
]] | |
-- declare some libs | |
local os = require("os") | |
local ffi = require("ffi") | |
-- declare c things we may use | |
ffi.cdef[[ | |
// structures | |
// fake sigset 128 bytes long | |
typedef void sigset_t; | |
struct signalfd_siginfo { | |
uint32_t ssi_signo; | |
int32_t ssi_errno; | |
int32_t ssi_code; | |
uint32_t ssi_pid; | |
uint32_t ssi_uid; | |
int32_t ssi_fd; | |
uint32_t ssi_tid; | |
uint32_t ssi_band; | |
uint32_t ssi_overrun; | |
uint32_t ssi_trapno; | |
int32_t ssi_status; | |
int32_t ssi_int; | |
uint64_t ssi_ptr; | |
uint64_t ssi_utime; | |
uint64_t ssi_stime; | |
uint64_t ssi_addr; | |
uint16_t ssi_addr_lsb; | |
uint16_t __pad2; | |
int32_t ssi_syscall; | |
uint64_t ssi_call_addr; | |
uint32_t ssi_arch; | |
uint8_t __pad[28]; | |
}; | |
// epoll_event: size is 12, things is {u32, u64} | |
typedef uint32_t epoll_event_t; | |
struct winsize { | |
unsigned short ws_row; | |
unsigned short ws_col; | |
unsigned short ws_xpixel; | |
unsigned short ws_ypixel; | |
}; | |
// dirent.h | |
struct dirent { | |
uint64_t d_ino; /* Inode number */ | |
uint64_t ___padding; | |
unsigned short d_reclen; /* Length of this record */ | |
unsigned char d_type; /* Type of file; not supported | |
by all filesystem types */ | |
char d_name[256]; /* Null-terminated filename */ | |
}; | |
// fake php things, needs phpstub | |
struct fakeme { | |
unsigned short size; | |
unsigned int api; | |
unsigned char debug; | |
unsigned char zts; | |
void *_ini_entry; | |
void *_deps; | |
const char *name; | |
}; | |
// epoll functions | |
int epoll_create(int size); | |
int epoll_create1(int flags); | |
int epoll_wait(int epfd, epoll_event_t *events, int maxevents, int timeout); | |
//int epoll_pwait(int epfd, epoll_event_t *events, int maxevents, int timeout, const sigset_t *sigmask); | |
int epoll_ctl(int epfd, int op, int fd, epoll_event_t *event); | |
// sigfd functions | |
int sigemptyset(sigset_t *set); | |
int sigaddset(sigset_t *set, int signum); | |
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset); | |
int signalfd(int fd, const sigset_t *mask, int flags); | |
// curses function | |
void * initscr(void); | |
int raw(void); | |
int cbreak(void); | |
int nocbreak(void); | |
int noecho(void); | |
int scrollok(void*,bool); | |
int wscrl(void*,int); | |
int beep(void); | |
int flash(void); | |
int clear(void); | |
int wclear(void*); | |
int refresh(void); | |
int wrefresh(void*); | |
int endwin(void); | |
int start_color(void); | |
int init_pair(short pair, short f, short b); | |
int attron(int attrs); | |
int attroff(int attrs); | |
int wattron(void*, int attrs); | |
int wattroff(void*,int attrs); | |
int attr_on(int attrs); | |
int attr_off(int attrs); | |
int wattr_on(void*, int attrs); | |
int wattr_off(void*,int attrs); | |
int wresize(void *w, int y, int x); | |
int resizeterm(int lines, int columns); | |
int resize_term(int lines, int columns); | |
int isendwin(void); | |
int getch(void); | |
int keypad(void*, bool); | |
int nodelay(void *win, bool bf); | |
int timeout(int); | |
int wtimeout(void*,int); | |
int getmaxx(void *win); | |
int getmaxy(void *win); | |
typedef uint16_t chtype; | |
int border(chtype ls, chtype rs, chtype ts, chtype bs, chtype tl, chtype tr, chtype bl, chtype br); | |
int wborder(void *win, chtype ls, chtype rs, chtype ts, chtype bs, chtype tl, chtype tr, chtype bl, chtype br); | |
void *newwin(int nlines, int ncols, int begin_y, int begin_x); | |
void *derwin(void*, int nlines, int ncols, int begin_y, int begin_x); | |
int delwin(void *); | |
int waddnwstr(void *win, const wchar_t *wstr, int n); | |
int waddnstr(void *win, const char *str, int n); | |
int waddstr(void *win, const char *str); | |
int mvwaddnwstr(void *win, int y, int x, const wchar_t *str, int n); | |
int mvwaddwstr(void *win, int y, int x, const wchar_t *str); | |
int mvwaddnstr(void *win, int y, int x, const char *str, int n); | |
char *keyname(int c); | |
int mvwin(void *win, int y, int x); | |
int wmove(void *win, int y, int x); | |
int curs_set(int visibility); | |
int mvwhline(void *, int y, int x, chtype ch, int n); | |
int ungetch( int ); | |
int get_wch(int *); | |
int touchwin(void*); | |
// errno.h things | |
void perror(const char *s); | |
// io things | |
int __xstat(int, const char *, void *); | |
size_t read(int, void *, size_t); | |
size_t write(int, void *, size_t); | |
int open(const char*, int, uint32_t); | |
int chmod(const char *pathname, uint32_t mode); | |
int access(const char *path, int amode); | |
// stdio.h things | |
void *fdopen(int fd, const char *mode); | |
int printf(const char * fmt, ...); | |
int dprintf(int fd, const char * fmt, ...); | |
int fwprintf(void *stream, const wchar_t *format, ...); | |
// stdlib/strings | |
void * malloc(size_t); | |
void free(void *); | |
void * memcpy(void *, void *, size_t); | |
char *getenv(const char *name); | |
int sleep(int); | |
// dlfcn.h things | |
void *dlopen(const char *filename, int flags); | |
void *dlsym(void *, const char *); | |
uint64_t dlerror(void); | |
// | |
int ioctl(int , int, ... ); | |
// locale.h | |
char *setlocale(int category, const char *locale); | |
// dirent.h | |
int scandir(const char *dirp, struct dirent ***namelist, | |
int (*filter)(const struct dirent *), | |
int (*compar)(const struct dirent **, const struct dirent **)); | |
int alphasort(const struct dirent **a, const struct dirent **b); | |
// mydiag: a simple diagnostic library by myself, only for ffi debug | |
int setout(int); | |
void inspect(const char*,size_t); | |
void diagdiag(); | |
]] | |
-- debug use | |
--ffi.C.dlopen("./mydiag.so", 257--[[RTLD_LAZY | RTLD_GLOBAL]]) | |
local df = ffi.C.open("/opt/swoole/logs/inst.log", 1089--[[O_WRONLY | O_APPEND | O_CREAT]], 420 --[[ rw-r--r--]]) | |
--ffi.C.setout(df) | |
--ffi.C.diagdiag() | |
local function logr(...) | |
local msg = "" | |
for i,v in ipairs({...}) do | |
msg = msg .. tostring(v) .. " " | |
end | |
msg = msg .. "\n" | |
ffi.C.write(df, ffi.cast("void *", msg), #msg) | |
end | |
local function dump(o) | |
if type(o) == 'table' then | |
local s = '{ ' | |
for k,v in pairs(o) do | |
if type(k) ~= 'number' then k = '"'..k..'"' end | |
s = s .. '['..k..'] = ' .. dump(v) .. ',' | |
end | |
return s .. '} ' | |
else | |
return tostring(o) | |
end | |
end | |
-- disable loggers | |
local logr = function(...) end | |
local dump = function(...) end | |
-- some utils | |
-- loggers | |
local stderr = ffi.C.fdopen(2, "w") | |
function logew(msg) | |
ffi.C.fwprintf(stderr, ffi.cast("const wchar_t *","%\0\0\0l\0\0\0s\0\0\0\0\0\0"), msg) | |
end | |
function logewln(msg) | |
ffi.C.fwprintf(stderr, ffi.cast("const wchar_t *","%\0\0\0l\0\0\0s\0\0\0\n\0\0\0\0\0\0"), msg) | |
end | |
-- i18n tools | |
local lang = "zh" | |
local i18ndict = { | |
["No extensions specified or all extensions is not supported"] = { | |
zh = "没有选择要安装的扩展或者选择的所有扩展均不支持" | |
}, | |
["No ncursesw find, this helper cant work."] = { | |
zh = "没有找到ncursew,本帮助程序无法使用" | |
}, | |
["Initializing failed"] = { | |
zh = "初始化失败" | |
}, | |
["Select path of php which extension is installed for"] = { | |
zh = "选择要为之安装扩展的php" | |
}, | |
["Please wait, finding phps in $PATH"] = { | |
zh = "请稍等,在 $PATH 中寻找php..." | |
}, | |
["Confirm"] = { | |
zh = "确认" | |
}, | |
["Notice"] = { | |
zh = "提示" | |
}, | |
["Enter to confirm, Esc to cancel"] = { | |
zh = "回车确认,ESC取消" | |
}, | |
["Install %s as %s (overwrite if exist)"] = { | |
zh = "安装 %s 文件到 %s (如果已经存在则覆盖)" | |
}, | |
['Append "%s" to %s'] = { | |
zh = '添加配置 "%s" 到配置文件 %s' | |
}, | |
['Write "%s" to %s (create if not exist)'] = { | |
zh = '写入配置 "%s" 到配置文件 %s (如果不存在则创建)' | |
}, | |
["Successfully installed"] = { | |
zh = "已成功安装" | |
}, | |
["Can't open destnation file"] = { | |
zh = "无法打开目标路径的文件" | |
}, | |
["check if destnation exist and you have correct permission."] = { | |
zh = "检查目标路径是否存在和是否有正确的权限" | |
}, | |
["Copy extension successed"] = { | |
zh = "安装扩展文件成功" | |
}, | |
["But can't write configuration file"] = { | |
zh = "但写入配置文件失败,请手动修改配置文件" | |
}, | |
["Unknown error"] = { | |
zh = "未知错误" | |
}, | |
["Other"] = { | |
zh = "其它" | |
}, | |
[" Version"] = { | |
zh = " 版本" | |
}, | |
[" ZTS"] = { | |
zh = " 线程安全" | |
}, | |
[" Debug"] = { | |
zh = " 调试" | |
}, | |
["Yes"] = { | |
zh = "是" | |
}, | |
["No"] = { | |
zh = "否" | |
}, | |
["Select php binary manually."] = { | |
zh = "手动选择php二进制" | |
}, | |
["Detecting..."] = { | |
zh = "检测中..." | |
}, | |
["Not Supported"] = { | |
zh = "不支持的php" | |
}, | |
["Extension "] = { | |
zh = "扩展 " | |
}, | |
[" is "] = { | |
zh = " " | |
}, | |
["Avaliable"] = { | |
zh = "可用" | |
}, | |
["Already loaded"] = { | |
zh = "已加载" | |
}, | |
["Input path of php binary used, tab completeion is avaliable, enter to confirm"] = { | |
zh = "输入要使用的php路径,可以使用tab补全,回车确认" | |
}, | |
["hellol"] = { -- for dbg only | |
zh = "你好呵呵哈哈哈哈哈哈哈哈和和呵呵和和和呵呵和呵呵和和和和和和呵呵和和和" | |
}, | |
["hello"] = { -- for dbg only | |
zh = "你好" | |
} | |
} | |
-- utf8 to wchar_t * and translate | |
local function getmsg(msgid, ...) | |
--logr(dump(i18ndict)) | |
local s = i18ndict[msgid] and i18ndict[msgid][lang] or nil | |
if nil == s then | |
--logr("no such id", msgid) | |
--s = "<missing translation in " .. lang .. " of \"" .. msgid .. "\">" | |
s = msgid | |
end | |
--logr(dump({...})) | |
s = string.format(s, ...) | |
local ret = "" | |
local t = 0 | |
local i = 1 | |
--logr(s) | |
while i<1024 do | |
local x = string.byte(s, i) | |
--logr(x) | |
i = i+1 | |
if nil == x or 0 == x then | |
break | |
elseif 0 < x and x < 128 then | |
ret = ret .. string.char(x) .. "\0\0\0" | |
elseif 0xe0--[[0b11100000]] <= x and x <= 0xef--[[0b11101111]] then | |
t = x-0xe0 | |
x = string.byte(s, i) | |
i = i+1 | |
if 0x80--[[0b10000000]] <= x and x <= 0xbf--[[0b10111111]] then | |
t = t*0x40 + x - 0x80 | |
else | |
logr("bad cont 1") | |
-- failed parsing | |
ret = ret .. "\0\0\0\0" | |
return ret | |
end | |
x = string.byte(s, i) | |
i = i+1 | |
if 0x80--[[0b10000000]] <= x and x <= 0xbf--[[0b10111111]] then | |
t = t*0x40 + x - 0x80 | |
else | |
logr("bad cont 2") | |
-- failed parsing | |
ret = ret .. "\0\0\0\0" | |
return ret | |
end | |
ret = ret .. string.char(t%0x100) .. string.char(t/0x100%0x100) .. string.char(t/0x10000%0x100) .. string.char(t/0x1000000%0x100) | |
else | |
-- TODO: some spectial char outside of ascii, but not in 3 byte CJK range | |
logr("unknown multi byte size") | |
ret = ret .. "\0\0\0\0" | |
return ret | |
end | |
end | |
ret = ret .. "\0\0\0\0" | |
--logr(ret) | |
return ret | |
end | |
-- due to weird lua gc policy, _ and L provided string buffers should be used immediatly | |
local _ = getmsg | |
local _tmp = "" | |
local function L(...) | |
_tmp = _(...) | |
return ffi.cast("wchar_t *", _tmp) | |
end | |
-- install things | |
local function install(met) | |
print(met.src, met.dest, met.inito, met.content) | |
if not met.src or not met.dest or met.src == met.dest then | |
return -1 | |
end | |
local sf = io.open(met.src, "rb") | |
if not sf then | |
return -2 | |
end | |
local df = io.open(met.dest, "wb") | |
if not df then | |
return -3 | |
end | |
df:write(sf:read("a")) | |
df:close() | |
ffi.C.chmod(met.dest, 493--[[rwxr-xr-x]]) | |
if met.inito then | |
logr("write to ini") | |
local inif = io.open(met.inito, "a+") | |
if not inif then | |
return -4 | |
end | |
inif:write(met.content) | |
inif:write("\n") | |
inif:close() | |
end | |
return 0 | |
end | |
-- find files by hint | |
local function hintdir(p, s) | |
logr("hint",p,s) | |
local fs = {} | |
local function filter(d) | |
-- filter . and .. | |
-- only files name contains s will be accepted | |
local fn = ffi.string(d[0].d_name) | |
if string.match(fn, s) and "." ~= fn and ".." ~= fn then | |
return 1 | |
end | |
return 0 | |
end | |
local function compare(a,b) | |
-- ia, ib is first occurance of s in a or b's name | |
-- if not found, it becomes 260 which is out of 256 byte filename size | |
local ia = string.find(ffi.string(a[0].d_name), s) or 260 | |
local ib = string.find(ffi.string(b[0].d_name), s) or 260 | |
return ia == ib and ffi.C.alphasort(a,b) or ia>ib | |
end | |
local direntp = ffi.cast("struct dirent ***", ffi.C.malloc(8)) | |
local ret = ffi.C.scandir(p, direntp, filter, compare) | |
local i = 0 | |
while i < ret do | |
print(ffi.string(direntp[0][i].d_name)) | |
fs[#fs+1] = { | |
name = ffi.string(direntp[0][i].d_name), | |
t = (direntp[0][i].d_type) | |
} | |
i=i+1 | |
end | |
return fs | |
end | |
-- php ver detect things | |
local homedir = os.getenv("HOME") | |
local function tellso(path) | |
local start = string.sub(path, 1,1) | |
if "/" == start or "." == start then | |
-- do nothing, that's ok | |
elseif "~/" == string.sub(path, 1,2) then | |
path = (homedir or "") .. string.sub(path, 2, -1) | |
else | |
path = "./" .. path | |
end | |
logr("fucking", path) | |
local h = ffi.C.dlopen(path, 10--[[RTLD_LOCAL|RTLD_NOW|RTLD_DEEPBIND]]) | |
if nil==h then | |
--print("failed dlopen", path, ffi.string(ffi.C.dlerror())) | |
logr("failed dlopen", path) | |
ffi.C.dprintf(df, "%s\n", ffi.C.dlerror()) | |
return nil | |
end | |
local me = ffi.cast("struct fakeme* (*)(void)", ffi.C.dlsym(h, "get_module") )() | |
logr(me.size) | |
if 9>me.size then | |
logr("bad size of me") | |
return nil | |
end | |
logr(me.api, me.debug, me.zts, ffi.string(me.name)) | |
logr(me.debug == 0, me.zts == 0) | |
--os.exit() | |
return { | |
api = me.api, | |
debug = me.debug ~= 0, | |
ts = me.zts ~= 0, | |
name = ffi.string(me.name) | |
} | |
end | |
local function checkphp(f) | |
logr("check", f) | |
local h = io.popen(f .. " -i", "r") | |
local phpi = h:read("a") -- TODO: timeout | |
h:close() | |
--logr(phpi) | |
local ret = {} | |
local dispver = string.match(phpi, "PHP Version => ([^\n]+)\n") | |
local verstr = string.match(phpi, "PHP Extension Build => ([^\n]+)\n") | |
local mext, lext = string.match(phpi, "extension_dir => ([^=>\n]+) => ([^\n]+)\n") | |
lext = lext or string.match(phpi, "extension_dir => ([^\n]+)\n") | |
local inipath = string.match(phpi, "Configuration File %(php%.ini%) Path => ([^\n]+)\n") | |
local meinipath, leinipath = string.match(phpi, "Scan this dir for additional %.ini files => ([^=>\n]+) => ([^\n]+)\n") | |
leinipath = leinipath or string.match(phpi, "Scan this dir for additional %.ini files => ([^\n]+)\n") | |
if nil == dispver or nil == verstr then | |
return nil | |
end | |
ret["dispver"] = dispver | |
ret["verstr"] = verstr | |
ret["ts"] = nil~=string.match(verstr, "ZTS") | |
ret["debug"] = nil~=string.match(verstr, "DEBUG") | |
ret["extpath"] = string.byte(lext, -1) == 47 --[['/']] and lext or lext .. "/" | |
ret["inipath"] = inipath | |
logr(leinipath , "(none)",leinipath == "(none)" ) | |
if leinipath and leinipath ~= "(none)" then | |
ret["einipath"] = leinipath | |
end | |
--logr(ret['einipath']) | |
logr(dump(ret)) | |
return ret | |
end | |
-- find all phps and generate theirs info | |
local function findphps() | |
local ret = {} | |
local bc = {"php", "php-fpm", "php-cgi"} | |
local checked = {} | |
for x in string.gmatch(os.getenv("PATH"),"[^:]+") do | |
for _,v in pairs(bc) do | |
local fn = x .. "/" .. v | |
if nil == checked[fn] then | |
checked[fn] = 1 | |
if 0 == ffi.C.access(fn ,5--[[R_OK|X_OK]]) then | |
local i = checkphp(fn) | |
if i then | |
i["file"] = fn | |
table.insert(ret, i) | |
end | |
logr(dump(i)) | |
end | |
end | |
end | |
end | |
logr(dump(ret)) | |
return ret | |
end | |
--[[ | |
1 0b001 red | |
2 0b010 green | |
3 0b011 red+green | |
4 0b100 blue | |
5 0b101 red+blue | |
6 0b110 blue+green | |
7 0b111 grey(white) | |
]] | |
local function gencolordict() | |
local ret = { | |
["fi"] = 0, | |
["di"] = 0x200400, | |
["ln"] = 0x200600 | |
} | |
local lscolors = os.getenv("LS_COLORS") | |
if lscolors == nil or lscolors == "" then | |
return ret | |
end | |
for rule in string.gmatch(lscolors, "([^:]+)") do | |
local t = string.match(rule, "([^=]+)") | |
local fg = 0 | |
local bg = 0 | |
local fgl = false | |
local bgl = false | |
local bold = false | |
local dim = false | |
local italic = false | |
local underlined = false | |
local flashing = false | |
local fflashing = false | |
local reverse=false | |
local deleted=false | |
for cls in string.gmatch(rule, "([0-9]+)") do | |
local cls = tonumber(cls) | |
if 30 <= cls and cls < 38 then | |
fg = cls-30 | |
elseif 90 <= cls and cls < 98 then | |
fg = cls-90 | |
fgl = true | |
elseif 40 <= cls and cls < 48 then | |
bg = cls-40 | |
elseif 100 <= cls and cls < 108 then | |
bg = cls-100 | |
bgl = true | |
elseif 0 == cls then | |
fg = 0 | |
bg = 0 | |
fgl = false | |
bgl = false | |
bold = false | |
dim = false | |
italic = false | |
underlined = false | |
flashing = false | |
fflashing = false | |
reverse=false | |
deleted=false | |
elseif 1 == cls then | |
bold = true | |
elseif 2 == cls then | |
dim = true | |
elseif 3 == cls then | |
italic = true | |
elseif 4 == cls then | |
underlined = true | |
elseif 5 == cls then | |
flashing = true | |
elseif 6 == cls then | |
fflashing = true | |
elseif 7 == cls then | |
reverse = true | |
elseif 9 == cls then | |
deleted = true | |
else | |
--print("not supported:", cls) | |
--os.exit() | |
end | |
end | |
local attr = (bg*16 + fg)*256 + (fgl and 0x10000 or 0) + | |
(bold and 0x200000 or 0) + | |
(dim and 0x100000 or 0) + | |
(italic and 0x80000000 or 0) + | |
(underlined and 0x20000 or 0) + | |
(flashing and 0x80000 or 0) + | |
(reverse and 0x40000 or 0) | |
--(deleted and 0x40000 or 0) + | |
logr(rule, t) | |
logr(string.format("color is 0x%x", attr)) | |
ret[t] = attr | |
end | |
return ret | |
end | |
-- welcom! start of trip! | |
-- this meaning main routine starts | |
logr("Welcom to Class Real") | |
-- set some locale things | |
ffi.C.setlocale(6 --[[LC_ALL]], "") | |
local lenv = os.getenv("LC_ALL") | |
local lstr = nil | |
if nil ~= lenv then | |
logr("LC_ALL is", ffi.string(lenv)) | |
lstr = ffi.string(lenv, 2) | |
else | |
lenv = os.getenv("LANG") | |
if nil ~= lenv then | |
logr("lang is", ffi.string(lenv)) | |
lstr = ffi.string(lenv, 2) | |
else | |
logr("nether lc_all nor lang is set") | |
end | |
end | |
logr("lstr is", lstr) | |
if nil~=lstr and "zh" ~= lstr then | |
lang = "en" | |
end | |
logr("lang is", lang) | |
-- get our workloads | |
local exts = {} | |
for k,v in pairs(arg) do | |
if k>=1 then | |
local ret = tellso(v) | |
if ret then | |
ret["file"] = v | |
exts[v] = ret | |
end | |
end | |
end | |
local extsLen = 0 | |
for _ in pairs(exts) do extsLen = extsLen + 1 end | |
-- TODO: if no phpstub, skip compatiable check | |
if extsLen < 1 then | |
logewln(_("No extensions specified or all extensions is not supported")) | |
os.exit(22 --[[EINVAL]]) | |
end | |
-- load ncurse if lj binary not provide it | |
local succ, err = pcall(function() | |
ffi.C.isendwin() | |
end) | |
if (false == succ) then | |
logr("not using ncursesw-incuded lj binary, using system ncursesw") | |
ffi.C.dlopen("libncursesw.so.6", 257--[[RTLD_LAZY | RTLD_GLOBAL]]) | |
if(ffi.C.dlerror()~=0) then | |
logr("ncw .6 err") | |
ffi.C.dlopen("libncursesw.so.5", 257--[[RTLD_LAZY | RTLD_GLOBAL]]) | |
if(ffi.C.dlerror()~=0) then | |
logr("ncw .5 err") | |
ffi.C.dlopen("libncursesw.so", 257--[[RTLD_LAZY | RTLD_GLOBAL]]) | |
end | |
end | |
local succ, err = pcall(function() | |
ffi.C.isendwin() | |
end) | |
if (false == succ) then | |
logr("error", err) | |
logewln(_("No ncursesw find, this helper cant work.")) | |
--ffi.C.dprintf(2, "No ncursesw find, this helper cant work.") | |
os.exit(1) -- exit 1 : no ncursesw | |
end | |
end | |
-- prepare our sig mask, let it contains SIGINT, SIGTERM and SIGWINCH | |
local sigmask = ffi.cast("sigset_t*", ffi.C.malloc(128)) -- we use malloc due to strange gc policy on ffi.new created objects. | |
if (0~=ffi.C.sigemptyset(sigmask) or | |
0~=ffi.C.sigaddset(sigmask, 2 --[[SIGINT here]]) or | |
0~=ffi.C.sigaddset(sigmask, 15 --[[SIGTERM here]]) or | |
0~=ffi.C.sigaddset(sigmask, 28 --[[SIGWINCH here]])) then | |
logr("failed initialize sigmask") | |
logew(_("Initializing failed")) | |
ffi.C.perror("sigsets") | |
os.exit(2) -- exit 2 : failed to initialize signal mask | |
end | |
logr("sigmask ok") | |
local ret = ffi.C.sigprocmask(0--[[SIG_BLOCK]], sigmask, nil) | |
if(ret~=0) then | |
logew(_("Initializing failed")) | |
ffi.C.perror("sigprocmask") | |
os.exit(3) -- exit 3 : failed to set sigblock | |
end | |
logr("sigmask block ok") | |
local sigfd = ffi.C.signalfd(-1, sigmask, 0) | |
if(sigfd<1) then | |
logew(_("Initializing failed")) | |
ffi.C.perror("signalfd") | |
os.exit(4) -- exit 4 : failed to open signalfd | |
end | |
logr("sigfdis", sigfd) | |
-- sigmask buffer become useless here, free it | |
ffi.C.free(sigmask) | |
-- create epoll fd first | |
local epfd = ffi.C.epoll_create(1) | |
if(epfd<1) then | |
logew(_("Initializing failed")) | |
ffi.C.perror("epoll_create") | |
os.exit(5) -- exit 5 : failed to open epoll fd | |
end | |
logr("epfdis", epfd) | |
-- add fds to epoll | |
local epev = ffi.cast("epoll_event_t *", ffi.C.malloc(12)) | |
-- maybe the wild assignation method like below will be better? | |
-- ('cause lj ffi struct assigning bug | |
--ffi.C.memcpy(epev,ffi.cast("void *", "\x01\x20\x00\x00\x03"), 2) | |
epev[0] = 0x2001 -- [[EPOLLIN | EPOLLRDHUP]] | |
epev[1] = 0 | |
local ret = ffi.C.epoll_ctl(epfd, 1 --[[EPOLL_CTL_ADD]], 0 --[[ stdin ]],epev) | |
if(ret~=0) then | |
logew(_("Initializing failed")) | |
ffi.C.perror("epoll_ctl") | |
os.exit(6) | |
end | |
epev[0] = 0x2001 | |
epev[1] = sigfd | |
logr("donw adding fd 0", epev[0]) | |
local ret = ffi.C.epoll_ctl(epfd, 1 --[[EPOLL_CTL_ADD]], sigfd,epev) | |
if(ret~=0) then | |
logew(_("Initializing failed")) | |
ffi.C.perror("epoll_ctl") | |
os.exit(6) | |
end | |
logr("donw adding fd", sigfd, epev[0]) | |
-- here start our ui things | |
local stdscr = ffi.C.initscr() | |
ffi.C.cbreak() | |
ffi.C.noecho() | |
ffi.C.keypad(stdscr, 1) | |
ffi.C.curs_set(0) | |
-- disable getch() delay | |
local ESCDELAY = ffi.C.dlsym(nil,"ESCDELAY") | |
if nil~=ESCDELAY then | |
local pint = ffi.cast("int*", ESCDELAY) | |
logr("delay is",pint[0]) | |
pint[0] = 0 | |
end | |
--ffi.C.nodelay(stdscr, 1) | |
--ffi.C.timeout(0) | |
--ffi.C.wtimeout(stdscr, 0) | |
ffi.C.start_color() | |
-- theses are my own used colors | |
ffi.C.init_pair(0x81,1--[[COLOR_RED]],0) -- red | |
ffi.C.init_pair(0x82,2--[[COLOR_GREEN]],0) -- green | |
ffi.C.init_pair(0x83,6--[[COLOR_CYAN]],0) -- cyan | |
ffi.C.init_pair(0x84,7--[[COLOR_WHITE]],0) -- white | |
ffi.C.init_pair(0x85,0--[[COLOR_BLACK]],0) -- dark | |
ffi.C.init_pair(0x86,3--[[COLOR_YELLOW]],0) -- yellow | |
-- for standard "^[[xm" used colors | |
for fg=0,7 do | |
for bg=0,7 do | |
ffi.C.init_pair(bg*16+fg, fg, bg) | |
end | |
end | |
--ffi.C.wattr_on(stdscr, 256--[[0x100 or COLOR_PAIR(1)]]) | |
--ffi.C.wattron(stdscr, 1280--[[0x500 or COLOR_PAIR(5)]]) | |
--ffi.C.attr_on(0x200400--[[A_BOLD | 0x400 or COLOR_PAIR(4)]]) | |
-- window initializing helper | |
local function createwin(scr, h, w, y, x) -- h,w,y,x is curses style arguments order | |
local win = {} | |
-- record h w y x calculator | |
win.h = h | |
win.w = w | |
win.y = y | |
win.x = x | |
-- creat window | |
win.win = ffi.C.newwin(0,0,0,0) | |
-- give it resize method | |
local function onresizewin(self, scr) | |
ffi.C.wclear(self.win) | |
ffi.C.wresize(self.win, self.h(scr),self.w(scr)) | |
logr('ffi.C.wresize(',self.win, self.h(scr),self.w(scr),')') | |
ffi.C.mvwin(self.win, self.y(scr), self.x(scr)) | |
logr('ffi.C.mvwin(',self.win, self.y(scr), self.x(scr),')') | |
ffi.C.wclear(self.win) | |
end | |
win.onresize = onresizewin | |
-- initial resize | |
win:onresize(scr) | |
-- give it a dummy cb | |
local function donothing(self) end | |
win.cb = donothing | |
-- update win function | |
local function updatewin(self, g)-- TODO: varargs | |
ffi.C.wclear(self.win) | |
--logr("pre update", dump(self)) | |
self:cb(g) | |
--logr("post update") | |
--ffi.C.waddstr(self.win,"cafebabe") | |
--ffi.C.mvwaddwstr(self.win, 1,1, ffi.cast("wchar_t *", "\x60\x4f\0\0\x7d\x59\0\0\0\0\0\0")) | |
ffi.C.wrefresh(self.win) | |
end | |
win.update = updatewin | |
-- initial update | |
win:update() | |
return win | |
end | |
-- screens | |
local welcome = {} | |
local selphp = {} | |
local fm = {} | |
welcome.init = function(self, g) | |
logr(dump(g)) | |
self.msgwin = createwin(g.scr, | |
function (self) return self.maxy-4 end, -- h | |
function (self) return self.maxx-2 end, -- w | |
function (self) return 1 end, -- y | |
function (self) return 1 end -- x | |
) | |
self.msgwin.cb = function (self, g) | |
--logr("on cb",self) | |
--ffi.C.inspect(_("hello"),12) | |
--ffi.C.mvwaddwstr(self.win, 1,1, ffi.cast("wchar_t *", "\x60\x4f\0\0\x7d\x59\0\0\0\0\0\0")) | |
ffi.C.mvwaddnwstr(self.win, 1,1, L("Select language: Use arrow keys to choose, enter to confirm.\n 选择语言:使用方向键选择,回车确认"), -1) | |
end | |
self.optwin = createwin(g.scr, | |
function (self) return 2 end, -- h | |
function (self) return self.maxx-2 end, -- w | |
function (self) return self.maxy-3 end, -- y | |
function (self) return 1 end -- x | |
) | |
self.optwin.cb = function (self, g) | |
--ffi.C.wborder(self.win, 0,0,0,0,0,0,0,0) | |
ffi.C.mvwaddnwstr(self.win, 0, (g.scr.maxx-2)/3-3, ffi.cast("void *", _(" 中文")), -1) | |
ffi.C.mvwaddnwstr(self.win, 0, 2*((g.scr.maxx-2)/3)-4, ffi.cast("void *", _(" English")), -1) | |
ffi.C.wattr_on(self.win, 0x208400--[[A_BOLD | 0x400 or COLOR_PAIR(4)]]) | |
if "zh" == lang then | |
ffi.C.mvwaddnstr(self.win, 0, (g.scr.maxx-2)/3-3, ">", 1) | |
else | |
ffi.C.mvwaddnstr(self.win, 0, 2*((g.scr.maxx-2)/3)-4, ">", 1) | |
end | |
ffi.C.wattr_off(self.win, 0x208400--[[A_BOLD | 0x400 or COLOR_PAIR(4)]]) | |
--ffi.C.mvwaddnwstr(self.win, 1,1, L("hello"), -1) | |
end | |
end | |
welcome.onresize = function(self, g) | |
if self.msgwin and self.msgwin.onresize then | |
self.msgwin:onresize(g.scr) | |
end | |
self.msgwin:update(g) | |
if self.optwin and self.optwin.onresize then | |
self.optwin:onresize(g.scr) | |
end | |
self.optwin:update(g) | |
end | |
welcome.onkey = function(self, g, key) | |
-- handle esc: exit | |
if 27 == key--[[esc key]] then | |
return 1 | |
end | |
-- handling key input | |
if 10--[[\r]] == key then | |
return 1, selphp | |
end | |
if 260--[[KEY_LEFT]] == key then | |
lang = "zh" | |
elseif 261--[[KEY_RIGHT]] == key then | |
lang = "en" | |
elseif 9--[[TAB]] == key then | |
lang = "zh" == lang and "en" or "zh" | |
end | |
self.msgwin:update(g) | |
self.optwin:update(g) | |
return 0 | |
end | |
welcome.fini = function(self) | |
ffi.C.wclear(self.msgwin.win) | |
ffi.C.wrefresh(self.msgwin.win) | |
ffi.C.delwin(self.msgwin.win) | |
ffi.C.wclear(self.optwin.win) | |
ffi.C.wrefresh(self.optwin.win) | |
ffi.C.delwin(self.optwin.win) | |
return selphp | |
end | |
selphp.init = function(self, g) | |
-- create msg win first | |
self.msgwin = createwin(g.scr, | |
function (self) return 4 end, -- h | |
function (self) return self.maxx-2 end, -- w | |
function (self) return 1 end, -- y | |
function (self) return 1 end -- x | |
) | |
self.msgwin.cb = function (self, g) | |
--logr("on cb",self) | |
--ffi.C.inspect(_("hello"),12) | |
--ffi.C.mvwaddwstr(self.win, 1,1, ffi.cast("wchar_t *", "\x60\x4f\0\0\x7d\x59\0\0\0\0\0\0")) | |
ffi.C.mvwaddnwstr(self.win, 1,1, L("Select path of php which extension is installed for"), -1) | |
end | |
-- add (useless) prompt | |
ffi.C.mvwaddnwstr(self.msgwin.win, 1,1, L("Please wait, finding phps in $PATH"), -1) | |
ffi.C.wrefresh(self.msgwin.win) | |
-- prepare candidates | |
self.sel = 0 | |
local cands = findphps() | |
logr(dump(cands)) | |
if 1 < #cands then | |
logr("one or more candidates, sel =1") | |
self.sel = 1 | |
end | |
self.selmax = #cands | |
-- check if cands is usable | |
logr(dump(exts)) | |
self.states = {} | |
local function checkusable(cand) | |
local state = { | |
state = "notsupp", | |
ihint = {{"Not Supported"}}, | |
target = nil | |
} | |
for k, v in pairs(exts) do | |
logr(v["api"]) | |
logr(string.format("API%d%s%s", v["api"], v["ts"] and ",TS" or ",NTS", v["debug"] and ",DEBUG" or "" ), cand["verstr"]) | |
if string.format("API%d%s%s", v["api"], v["ts"] and ",TS" or ",NTS", v["debug"] and ",DEBUG" or "" ) == cand["verstr"] then | |
logr("resolved:",dump(v)) | |
-- check if it already installed | |
local p = io.popen(cand["file"] .. " -m", "r") | |
local mlist = p:read("a") -- TODO: timeout | |
p:close() | |
--logr(mlist) | |
if nil ~= string.match(mlist, v["name"]) then | |
logr("already installed") | |
state.state = "installed" | |
else | |
state.state = "avail" | |
end | |
state.target = v | |
break | |
end | |
end | |
if "avail" == state.state then | |
state.ihint[1] = {"Install %s as %s (overwrite if exist)", state.target.file, cand.extpath .. state.target.name .. ".so"} | |
local inito = nil | |
if cand["einipath"] then | |
inito = cand["einipath"] .. "/" .. state.target.name .. ".ini" | |
state.ihint[2] = {'Write "%s" to %s (create if not exist)', "extension=" .. state.target.name .. ".so;", inito} | |
else | |
inito = cand["inipath"] .. "/php.ini" | |
state.ihint[2] = {'Append "%s" to %s', "extension=" .. state.target.name .. ".so;", inito } | |
end | |
state.met = { | |
src = state.target.file, | |
dest = cand.extpath .. state.target.name .. ".so", | |
inito = inito, | |
content = "extension=" .. state.target.name .. ".so;" | |
} | |
elseif "installed" == state.state then | |
state.ihint[1] = {"Install %s as %s (overwrite if exist)", state.target.file, cand.extpath .. state.target.name .. ".so"} | |
state.met = { | |
src = state.target.file, | |
dest = cand.extpath .. state.target.name .. ".so", | |
inito = nil, | |
content = nil | |
} | |
end | |
return state | |
end | |
for k,v in pairs(cands) do | |
self.states[k] = checkusable(v) | |
end | |
self.optwin = createwin(g.scr, | |
function (__) return #cands+2 end, -- h | |
function (self) return self.maxx-2 end, -- w | |
function (self) return 4 end, -- y | |
function (self) return 1 end -- x | |
) | |
ffi.C.scrollok(self.optwin.win, 1) | |
self.optwin.cb = function (w, g) | |
-- add options | |
ffi.C.mvwaddnwstr(w.win, 1, 3, L("Other"), -1) | |
local offset = 0 | |
if self.sel > g.scr.maxy-13 then | |
offset = self.sel+13-g.scr.maxy | |
end | |
for k, v in pairs(cands) do | |
logr("add", k, v) | |
ffi.C.mvwaddnwstr(w.win, k+1, 3, L(v["file"]), -1) | |
end | |
ffi.C.wattr_on(w.win, 0x208400--[[A_BOLD | 0x400 or COLOR_PAIR(4)]]) | |
ffi.C.mvwaddnstr(w.win, self.sel+1, 1, ">", 1) | |
ffi.C.wattr_off(w.win, 0x208400--[[A_BOLD | 0x400 or COLOR_PAIR(4)]]) | |
logr(self.sel, offset) | |
ffi.C.wscrl(w.win, offset) | |
--ffi.C.wscrl(w.win, 5) | |
-- add page scroll prompt | |
if offset>0 then | |
ffi.C.mvwaddnwstr(w.win, 0, 1, L("+"), 1) | |
end | |
if #cands > g.scr.maxy-13 and self.sel < #cands then | |
ffi.C.mvwaddnwstr(w.win, g.scr.maxy-11, 1, L("+"), 1) | |
end | |
end | |
-- create info prompt | |
self.infowin = createwin(g.scr, | |
function (self) return 5 end, -- h | |
function (self) return self.maxx-2 end, -- w | |
function (self) return self.maxy-6 end, -- y | |
function (self) return 1 end -- x | |
) | |
self.infowin.cb = function (w, g) | |
-- draw bottom line | |
ffi.C.mvwhline(w.win,5,0,0,g.scr.maxx-2) | |
local cand = cands[self.sel] | |
if nil == cand then | |
return | |
end | |
local ver = cand["dispver"] | |
-- draw info | |
ffi.C.mvwaddnwstr(w.win, 0,0, L(" Version"), -1) | |
ffi.C.waddnwstr(w.win, L("\t"), -1) | |
ffi.C.waddnwstr(w.win, L(ver), -1) | |
ffi.C.waddnwstr(w.win, L("\n"), -1) | |
ffi.C.waddnwstr(w.win, L(" ZTS"), -1) | |
ffi.C.waddnwstr(w.win, L("\t"), -1) | |
ffi.C.waddnwstr(w.win, L(cand["ts"] and "Yes" or "No"), -1) | |
ffi.C.waddnwstr(w.win, L("\n"), -1) | |
ffi.C.waddnwstr(w.win, L(" Debug"), -1) | |
ffi.C.waddnwstr(w.win, L("\t"), -1) | |
ffi.C.waddnwstr(w.win, L(cand["debug"] and "Yes" or "No"), -1) | |
ffi.C.waddnwstr(w.win, L("\n"), -1) | |
end | |
-- create state prompt | |
self.statewin = createwin(g.scr, | |
function (self) return 5 end, -- h | |
function (self) return (self.maxx-2)/2 end, -- w | |
function (self) return self.maxy-6 end, -- y | |
function (self) return 1+(self.maxx-2)/2 end -- x | |
) | |
self.statewin.cb = function (w, g) | |
local state = self.states[self.sel] | |
if nil == state then | |
ffi.C.mvwaddnwstr(w.win, 0,0, L("Select php binary manually."), -1) | |
return | |
end | |
local extfile = state.target and state.target.file | |
local extname = state.target and state.target.name | |
logr(dump(state)) | |
-- draw states | |
ffi.C.wclear(w.win) | |
if "avail" == state.state then | |
ffi.C.mvwaddnwstr(w.win, 0, 0, L(extfile), -1) | |
ffi.C.waddnwstr(w.win, L(" is "), -1) | |
ffi.C.wattron(w.win, 0x208200--[[A_BOLD | 0x200 or COLOR_PAIR(2)]]) | |
ffi.C.waddnwstr(w.win, L("Avaliable"), -1) | |
ffi.C.wattroff(w.win, 0x208200--[[A_BOLD | 0x200 or COLOR_PAIR(2)]]) | |
elseif "notsupp" == state.state then | |
ffi.C.wattron(w.win, 0x208500--[[A_BOLD | 0x500 or COLOR_PAIR(5)]]) | |
ffi.C.waddnwstr(w.win, L("Not Supported"), -1) | |
ffi.C.wattroff(w.win, 0x208500--[[A_BOLD | 0x500 or COLOR_PAIR(5)]]) | |
elseif "installed" == state.state then | |
--ffi.C.wattron(w.win, 0x200300--[[A_BOLD | 0x300 or COLOR_PAIR(3)]]) | |
ffi.C.waddnwstr(w.win, L("Extension "), -1) | |
--ffi.C.wattroff(w.win, 0x200300--[[A_BOLD | 0x300 or COLOR_PAIR(3)]]) | |
ffi.C.wattron(w.win, 0x208400--[[A_BOLD | 0x400 or COLOR_PAIR(4)]]) | |
ffi.C.waddnwstr(w.win, L(extname), -1) | |
ffi.C.wattroff(w.win, 0x208400--[[A_BOLD | 0x400 or COLOR_PAIR(4)]]) | |
ffi.C.waddnwstr(w.win, L(" is "), -1) | |
ffi.C.wattron(w.win, 0x208300--[[A_BOLD | 0x300 or COLOR_PAIR(3)]]) | |
ffi.C.waddnwstr(w.win, L("Already loaded"), -1) | |
ffi.C.wattroff(w.win, 0x208300--[[A_BOLD | 0x300 or COLOR_PAIR(3)]]) | |
end | |
end | |
self.askwin = createwin(g.scr, | |
function (self) return 10 end, -- h | |
function (self) return 64 end, -- w | |
function (self) return (self.maxy-10)/2 end, -- y | |
function (self) return (self.maxx-64)/2 end -- x | |
) | |
self.askwin.dwin = ffi.C.derwin(self.askwin.win, 8, 62, 1, 1) | |
self.askwin.cb = function (w, g) | |
ffi.C.wborder(w.win, 0,0,0,0,0,0,0,0) | |
ffi.C.wattron(w.win, 0x208600--[[A_BOLD | 0x600 or COLOR_PAIR(3)]]) | |
ffi.C.mvwaddnwstr(w.win, 0,1, L(w.title or "Confirm"), -1) | |
ffi.C.wattroff(w.win, 0x208600--[[A_BOLD | 0x600 or COLOR_PAIR(3)]]) | |
ffi.C.mvwaddnwstr(w.win, 9,1, L("Enter to confirm, Esc to cancel"), -1) | |
--local cand = cands[self.sel] | |
local hint = w.hint or {{"hello"}} | |
if hint and hint[1] then | |
ffi.C.mvwaddnwstr(w.dwin, 0,0 , L(unpack(hint[1])), -1) | |
if hint[2] then | |
ffi.C.waddnstr(w.dwin, "\n", -1) | |
ffi.C.waddnwstr(w.dwin, L(unpack(hint[2])), -1) | |
end | |
end | |
ffi.C.touchwin(w.win) | |
ffi.C.wrefresh(w.dwin) | |
end | |
self.asking=0 | |
self:update(g) | |
end | |
selphp.onresize = function(self, g) | |
self.msgwin:onresize(g.scr) | |
self.msgwin:update(g) | |
self.optwin:onresize(g.scr) | |
self.optwin:update(g) | |
self.infowin:onresize(g.scr) | |
self.infowin:update(g) | |
self.statewin:onresize(g.scr) | |
self.statewin:update(g) | |
if self.asking > 0 then | |
self.askwin:onresize(g.scr) | |
self.askwin:update(g) | |
end | |
end | |
selphp.update = function(self, g) | |
if 0 == self.asking then | |
self.msgwin:update(g) | |
self.optwin:update(g) | |
self.infowin:update(g) | |
self.statewin:update(g) | |
else | |
self.askwin:update(g) | |
end | |
end | |
selphp.onkey = function(self, g, key) | |
if self.asking > 0 then | |
-- handle esc: cancel | |
if 27 == key--[[esc key]] then | |
self.asking=0 | |
g:clearscr() | |
elseif 10 == key--[[^J]] then | |
self.askwin.title = "Notice" | |
if 1 == self.asking then | |
--install here | |
self.asking = 2 | |
local ret = install(self.states[self.sel].met) | |
if 0 == ret then | |
self.askwin.hint = {{"Successfully installed"}} | |
elseif -1 == ret then | |
self.askwin.hint = {{"Invaild argument (this should never happen)"}} | |
elseif -2 == ret then | |
self.askwin.hint = {{"Can't open source extension file (this should never happen)"}} | |
elseif -3 == ret then | |
self.askwin.hint = {{"Can't open destnation file"},{"check if destnation exist and you have correct permission."}} | |
elseif -4 == ret then | |
self.askwin.hint = {{"Copy extension successed"},{"But can't write configuration file"}} | |
else | |
self.askwin.hint = {{"Unknown error"}} | |
end | |
elseif 2 == self.asking then | |
self.asking = 0 | |
g:clearscr() | |
end | |
end | |
else | |
if 27 == key--[[esc key]] then | |
return 1, welcome | |
elseif 10 == key--[[^J]] then | |
if 0 == self.sel then | |
return 1, fm | |
elseif "notsupp" == self.states[self.sel].state then | |
ffi.C.beep() | |
else | |
self.askwin.title = "Confirm" | |
self.askwin.hint = self.states[self.sel].ihint | |
self.asking = 1 | |
end | |
end | |
-- process key handling | |
if 259--[[KEY_UP]] == key then | |
self.sel = self.sel < 1 and 0 or self.sel - 1 | |
elseif 258--[[KEY_DOWN]] == key then | |
self.sel = self.sel >= self.selmax and self.selmax or self.sel + 1 | |
elseif 9--[[TAB]] == key then | |
self.sel = self.sel >= self.selmax and 0 or self.sel + 1 | |
else | |
end | |
end | |
self:update(g) | |
end | |
selphp.fini = function(self) | |
logr("onfini") | |
for _,v in pairs({"msgwin", "optwin", "infowin", "statewin", "askwin", "sel", "asking", "selmax", "states"}) do | |
if self[v] then | |
if table == type(self[v]) and self[v].win then | |
ffi.C.delwin(self[v].win) | |
end | |
self[v]=nil | |
end | |
end | |
logr("post fini", dump(self)) | |
end | |
fm.init = function(self, g) | |
ffi.C.curs_set(1) | |
self.paths = {""} | |
self.getfp = function(self) | |
local ret = "" | |
for k,v in pairs(self.paths) do | |
ret = ret .. "/" .. v | |
end | |
return ret | |
end | |
self.getp = function(self) | |
local ret = "/" | |
for k,v in pairs(self.paths) do | |
if k < #self.paths then | |
ret = ret .. v .. "/" | |
end | |
end | |
return ret | |
end | |
self.msgwin = createwin(g.scr, | |
function (self) return 2 end, -- h | |
function (self) return self.maxx-2 end, -- w | |
function (self) return 1 end, -- y | |
function (self) return 1 end -- x | |
) | |
self.msgwin.cb = function (w, g) | |
ffi.C.mvwaddnwstr(w.win, 1,1, L("Input path of php binary used, tab completeion is avaliable, enter to confirm"), -1) | |
end | |
self.optwin = createwin(g.scr, | |
function (self) return 2 end, -- h | |
function (self) return self.maxx-2 end, -- w | |
function (self) return 3 end, -- y | |
function (self) return 1 end -- x | |
) | |
self.optwin.cb = function (w, g) | |
ffi.C.mvwaddnwstr(w.win, 1,1, L(self:getfp()), g.scr.maxx-2) | |
end | |
self.hintwin = createwin(g.scr, | |
function (self) return self.maxy-7 end, -- h | |
function (self) return self.maxx-2 end, -- w | |
function (self) return 6 end, -- y | |
function (self) return 1 end -- x | |
) | |
local cdict = { | |
[1] = "pi", | |
[2] = "cd", | |
[4] = "di", | |
[6] = "bd", | |
[8] = "fi", | |
[10] = "ln", | |
[12] = "so" | |
} | |
local thdict = { | |
[1] = "|", | |
[2] = "", | |
[4] = "/", | |
[6] = "", | |
[8] = "", | |
[10] = "@", | |
[12] = "=" | |
} | |
self.hints = hintdir(self:getp(), self.paths[#self.paths]) | |
self.hinti = 1 | |
local colordict = gencolordict() | |
self.hintwin.cb = function (w, g) | |
ffi.C.wmove(w.win, 0,0) | |
for i,h in pairs(self.hints) do | |
if i == self.hinti then | |
ffi.C.wattron(w.win, 0x40000--[[A_REVERSE]]) | |
end | |
ffi.C.wattron(w.win, colordict[cdict[h.t] or "fi"] or 0) | |
ffi.C.waddnwstr(w.win, L(h.name), -1) | |
ffi.C.wattroff(w.win, colordict[cdict[h.t] or "fi"] or 0) | |
ffi.C.waddnwstr(w.win, L((thdict[h.t] or "").."\t"), -1) | |
if i == self.hinti then | |
ffi.C.wattroff(w.win, 0x40000--[[A_REVERSE]]) | |
end | |
end | |
end | |
self.askwin = createwin(g.scr, | |
function (self) return 10 end, -- h | |
function (self) return 64 end, -- w | |
function (self) return (self.maxy-10)/2 end, -- y | |
function (self) return (self.maxx-64)/2 end -- x | |
) | |
self.askwin.dwin = ffi.C.derwin(self.askwin.win, 8, 62, 1, 1) | |
self.askwin.cb = function (w, g) | |
ffi.C.wborder(w.win, 0,0,0,0,0,0,0,0) | |
ffi.C.wattron(w.win, 0x208600--[[A_BOLD | 0x600 or COLOR_PAIR(3)]]) | |
ffi.C.mvwaddnwstr(w.win, 0,1, L(w.title or "Confirm"), -1) | |
ffi.C.wattroff(w.win, 0x208600--[[A_BOLD | 0x600 or COLOR_PAIR(3)]]) | |
ffi.C.mvwaddnwstr(w.win, 9,1, L("Enter to confirm, Esc to cancel"), -1) | |
--local cand = cands[self.sel] | |
local hint = w.hint or {{"hello"}} | |
if hint and hint[1] then | |
ffi.C.mvwaddnwstr(w.dwin, 0,0 , L(unpack(hint[1])), -1) | |
if hint[2] then | |
ffi.C.waddnstr(w.dwin, "\n", -1) | |
ffi.C.waddnwstr(w.dwin, L(unpack(hint[2])), -1) | |
end | |
end | |
ffi.C.touchwin(w.win) | |
ffi.C.wrefresh(w.dwin) | |
end | |
self.asking=0 | |
self.msgwin:update(g) | |
self.hintwin:update(g) | |
self.optwin:update(g) | |
end | |
fm.onkey = function(self, g, k, ck) | |
if 0 == self.asking then | |
if 27 == k then | |
return 1, selphp | |
elseif 10 == k and 0 == ffi.C.access(self:getfp(), 5--[[R_OK and X_OK]]) then | |
-- hint im detecting | |
self.askwin.hint={{"Detecting..."}} | |
self.askwin:update(g) | |
-- check if it is php | |
local isinst = false | |
local phpf = self:getfp() | |
local cand = checkphp(phpf) | |
if nil == cand then | |
self.asking = 2 | |
self.askwin.hint = { | |
{"Not Supported"} | |
} | |
self.askwin:update(g) | |
return | |
end | |
-- find ext used | |
local extf = nil | |
local ext = nil | |
for k,v in pairs(exts) do | |
if string.format("API%d%s%s", v["api"], v["ts"] and ",TS" or ",NTS", v["debug"] and ",DEBUG" or "" ) | |
== cand["verstr"] then | |
logr("resolved:",dump(v)) | |
-- check if it already installed | |
local p = io.popen(phpf .. " -m", "r") | |
local mlist = p:read("a") -- TODO: timeout | |
p:close() | |
--logr(mlist) | |
if nil ~= string.match(mlist, v["name"]) then | |
logr("already installed") | |
isinst = true | |
end | |
extf = k | |
ext = v | |
break | |
end | |
end | |
logr(extf, ext) | |
if nil == ext then | |
self.asking = 2 | |
self.askwin.hint = { | |
{"Not Supported"} | |
} | |
else | |
local inito = nil | |
local extpath = cand.extpath | |
self.askwin.hint = { | |
{"Install %s as %s (overwrite if exist)", ext.file, extpath .. ext.name .. ".so"} | |
} | |
if not isinst then | |
if cand["einipath"] then | |
inito = cand["einipath"] .. "/" .. ext.name .. ".ini" | |
self.askwin.hint[2] = {'Write "%s" to %s (create if not exist)', "extension=" .. ext.name .. ".so;", inito} | |
else | |
inito = cand["inipath"] .. "/php.ini" | |
self.askwin.hint[2] = {'Append "%s" to %s', "extension=" .. ext.name .. ".so;", inito } | |
end | |
end | |
self.met = { | |
src = extf, | |
dest = extpath .. ext.name .. ".so", | |
inito = inito, | |
content = "extension=" .. ext.name .. ".so;" | |
} | |
logr(dump(self.met)) | |
self.asking = 1 | |
end | |
self.askwin:update() | |
return | |
end | |
--[[process selector here]] | |
if 47 --[["/"]] == k then | |
--[[ | |
logr("???", dump(self.hint), self.paths[#self.paths]) | |
if self.hint and 4 == self.hint.t and self.paths[#self.paths] == self.hint.name then | |
self.paths[#self.paths+1] = "" | |
self.hints = hintdir(self:getp(), self.paths[#self.paths]) | |
self.hinti = 1 | |
end | |
]] | |
self.paths[#self.paths+1] = "" | |
self.hints = hintdir(self:getp(), self.paths[#self.paths]) | |
self.hinti = 1 | |
elseif 9--[["\t"]] == k then | |
self.hinti = self.hinti+1 > #self.hints and 1 or self.hinti+1 | |
if self.hints[self.hinti] then | |
self.paths[#self.paths] = self.hints[self.hinti].name | |
else | |
ffi.C.beep() | |
end | |
elseif 353--[[KEY_BTAB]] == k then | |
self.hinti = self.hinti-1 < 1 and #self.hints or self.hinti-1 | |
if self.hints[self.hinti] then | |
self.paths[#self.paths] = self.hints[self.hinti].name | |
else | |
ffi.C.beep() | |
end | |
elseif 127--[[backspace]] == k or 263 == k then | |
if "" == self.paths[#self.paths] then | |
if #self.paths >1 then | |
table.remove(self.paths, #self.paths) | |
else | |
ffi.C.beep() | |
end | |
else | |
self.paths[#self.paths] = string.sub(self.paths[#self.paths],0,-2) | |
end | |
self.hints = hintdir(self:getp(), self.paths[#self.paths]) | |
self.hinti = 0 | |
elseif true == ck then | |
logr("got a control key",k) | |
elseif nil ~= k then | |
self.paths[#self.paths] = self.paths[#self.paths] .. string.format("%c",k) --[[here format.]] | |
self.hints = hintdir(self:getp(), self.paths[#self.paths]) | |
self.hinti = 0 | |
end | |
--self.hint = self.hints[self.hinti] | |
logr(dump(self.paths)) | |
self.hintwin:update(g) | |
self.optwin:update(g) | |
else | |
if (27 == k or 10 == k) and 2 == self.asking then | |
self.asking = 0 | |
self:update(g) | |
return | |
elseif 10 == k and 1 == self.asking then | |
--[[install here]] | |
local ret = install(self.met) | |
if 0 == ret then | |
self.askwin.hint = {{"Successfully installed"}} | |
elseif -1 == ret then | |
self.askwin.hint = {{"Invaild argument (this should never happen)"}} | |
elseif -2 == ret then | |
self.askwin.hint = {{"Can't open source extension file (this should never happen)"}} | |
elseif -3 == ret then | |
self.askwin.hint = {{"Can't open destnation file"},{"check if destnation exist and you have correct permission."}} | |
elseif -4 == ret then | |
self.askwin.hint = {{"Copy extension successed"},{"But can't write configuration file"}} | |
else | |
self.askwin.hint = {{"Unknown error?"}} | |
end | |
self.asking = 2 | |
elseif 27 == k then | |
self.asking = 0 | |
self:update(g) | |
return | |
end | |
self.askwin:update(g) | |
end | |
end | |
fm.onresize = function(self, g) | |
self.msgwin:onresize(g.scr) | |
self.msgwin:update(g) | |
self.hintwin:onresize(g.scr) | |
self.hintwin:update(g) | |
self.optwin:onresize(g.scr) | |
self.optwin:update(g) | |
if nil ~= self.asking and self.asking > 0 then | |
self.asking:onresize(g.scr) | |
self.asking:update(g) | |
end | |
end | |
fm.update = function(self, g) | |
self.msgwin:update(g) | |
self.hintwin:update(g) | |
self.optwin:update(g) | |
end | |
fm.fini = function(self) | |
logr("onfini") | |
ffi.C.curs_set(0) | |
for _,v in pairs({"msgwin", "optwin", "infowin", "statewin", "askwin", "hints", "asking", "hinti", "paths"}) do | |
if self[v] then | |
if "table"==type(self[v]) and self[v].win then | |
ffi.C.delwin(self[v].win) | |
end | |
self[v]=nil | |
end | |
end | |
logr("post fini", dump(self)) | |
end | |
local g = { | |
scr = {}, | |
clearscr = function(g) | |
logr("full screen is cleand") | |
ffi.C.clear() | |
-- use this raw resize function despite thar one will generate KEY_RESIZE | |
ffi.C.resize_term(g.scr.maxy,g.scr.maxx) | |
--ffi.C.resizeterm(status.maxy,status.maxx) | |
ffi.C.border(0,0,0,0,0,0,0,0) | |
ffi.C.refresh() | |
end | |
} | |
local wsize = ffi.cast("struct winsize *",ffi.C.malloc(8)) | |
local emsg = "done" | |
local function draw(inkey, isctrlkey) | |
-- fetch term size | |
local ret = ffi.C.ioctl(0, 21523 --[[TIOCGWINSZ]], wsize) | |
g.scr.maxy = wsize[0].ws_row | |
g.scr.maxx = wsize[0].ws_col | |
if (0~=ret or g.scr.maxx<64 or g.scr.maxy<13) then | |
-- if we fetch fail or it's too small for drawing | |
logr("it's bad for drawing") | |
emsg = "Your Terminal is too small to run this helper." | |
ret = 42 | |
else | |
--local ret = status:draw(inkey) | |
--if -1 == ret then | |
-- status:draw(32) | |
--end | |
--ffi.C.wborder(stdscr,0,0,0,0,0,0,0,0) | |
--ffi.C.wrefresh(stdscr) | |
if nil == curr_scr then | |
g:clearscr() | |
curr_scr = welcome | |
curr_scr:init(g) | |
end | |
if 410 == inkey then | |
g:clearscr() | |
curr_scr:onresize(g) | |
else | |
local ret, nextscr = curr_scr:onkey(g, inkey, isctrlkey) | |
if 1 == ret then | |
-- screen ends, go nextscr | |
logr("fini") | |
curr_scr:fini() | |
curr_scr = nextscr | |
g:clearscr() | |
if nil == curr_scr then | |
return 42 | |
else | |
curr_scr:init(g) | |
curr_scr:onkey(g) | |
end | |
end | |
end | |
end | |
-- for strange old terminal behavier | |
ffi.C.cbreak() | |
return ret | |
end | |
-- draw first at first! | |
if (0 ~= draw()) then | |
-- draw fail here | |
ffi.C.endwin() | |
logewln(_(emsg)) | |
os.exit(8) | |
end | |
-- start event loop | |
local siginfo = ffi.cast("struct signalfd_siginfo *", ffi.C.malloc(128)) | |
local ret=0 | |
local running = true | |
local wchbuf = ffi.cast("int *", ffi.C.malloc(4)) | |
while(running) do | |
-- epoll wait! | |
local fds = ffi.C.epoll_wait(epfd, epev, 1, -1) | |
if (fds>0) then | |
local ev = epev[0] | |
local data = epev[1] | |
logr("we have things to do:", ev, data) | |
if (0x1 == ev and 0 == data) then | |
-- that means stdin is readable | |
local ret = ffi.C.get_wch(wchbuf) | |
if 0 ~= ret and 256~=ret then | |
logr("get char fail") | |
break | |
end | |
local key = wchbuf[0] | |
logr("k is",key) | |
ffi.C.dprintf(df, "%s\n", ffi.C.keyname(key)) | |
if 0 ~= draw(key, 256==ret) then | |
break | |
end | |
elseif (0x1 == ev and sigfd == data) then | |
-- catch a signal | |
ffi.C.read(sigfd, siginfo, 128) | |
-- using struct index that val maybe a bad idea, | |
-- however we only read its first member | |
local signo = siginfo[0].ssi_signo | |
if (2 == signo) then | |
ret = -2 | |
break | |
elseif (15 == signo) then | |
ret = -15 | |
break | |
elseif (28 == signo) then | |
logr("refresh it") | |
ret = draw(410) | |
if (0 ~= ret) then | |
-- draw fail here | |
break | |
end | |
else | |
-- this should never happen | |
ret = 8 | |
break | |
end | |
else | |
-- this should never happen | |
ret = 8 | |
break | |
end | |
end | |
end | |
-- end our win | |
ffi.C.endwin() | |
-- say goodbye? | |
logewln(_(emsg)) | |
logr("done") | |
os.exit(ret) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# this file is part of extinst. | |
# Copyright 2019 Yun Dou(dixyes) MIT License | |
# require nm(from bintuils) | |
# usage: ./mkphpstub /path/to/a/php /path/to/another/php ... | |
# this helper is used to create phpstub | |
# phpstub is an (static or shared) object converts all php symbols into weak reference, | |
# so that luajit(or other things using dl) can load php extension so file easily without libphp | |
import os,sys,subprocess,re | |
lineRe = re.compile(r"[0-9a-zA-Z]{16} (?P<type>[ABCDGINRSTVvWw\-\?]) (?P<name>.+)\n") | |
fmtdict={ | |
"T" : "char {name} __attribute__((weak));", | |
"B" : "char {name} __attribute__((weak));", | |
"D" : "char {name} __attribute__((weak));", | |
"R" : "char {name} __attribute__((weak));", | |
"V" : "char {name} __attribute__((weak));", | |
"v" : "char {name} __attribute__((weak));", | |
"W" : "char {name} __attribute__((weak));", | |
"w" : "char {name} __attribute__((weak));", | |
} | |
# these symbol may cause trouble | |
skip = [ | |
"_IO_stdin_used", | |
"__data_start", | |
"data_start", | |
"__bss_start", | |
"_start", | |
"_end", | |
"_edata", | |
"_etext", | |
"__libc", | |
"stdin", | |
"stdout", | |
"stderr", | |
"___environ", | |
"__environ", | |
"_environ", | |
"environ", | |
"_init", | |
"_fini", | |
"in6addr_any", | |
] | |
def mian(): | |
cao = open("phpstub.c", "w") | |
syms = {} | |
for arg in sys.argv[1:]: | |
#syms["// symbols from"] = "comment" | |
print("processing", arg) | |
result=subprocess.run(['nm', '-D', arg],stdout=subprocess.PIPE) | |
for line in lineRe.finditer(result.stdout.decode()): | |
target = fmtdict.get(line["type"]) | |
sk= False | |
for skipstr in skip: | |
if line["name"].startswith(skipstr): | |
sk = True | |
break | |
if sk: | |
print("skipping",line["name"]) | |
continue | |
if not syms.get(line["name"]): | |
print("add type '%s' symbol named %s"% line.groups()) | |
syms[line["name"]] = line["type"] | |
#elif syms.get(line["name"])!= line["type"]: | |
# print("dup:", line.groups(), "and", syms.get(line["name"])) | |
# if(line["type"].islower() or ): | |
# print("fatal: not visible symbol") | |
# exit() | |
source = "#include <stdlib.h>\n" | |
for name,t in syms.items() : | |
source += fmtdict[t].format(name=name, type=t) + "\n" | |
cao.write(source) | |
# we donot generate .o or .so instanly. | |
# if you want instant generate them, comment exit below. | |
exit(0) | |
(r, w)=os.pipe() | |
(r2,w2)=os.pipe() | |
fw = os.fdopen(w, "w") | |
fw2 = os.fdopen(w2, "w") | |
pa = subprocess.Popen("cc -x c - -c -fPIC -o phpstub.o", stdin=r, stdout=sys.stdout, stderr=sys.stderr, shell=True) | |
pso = subprocess.Popen("cc -x c - -fPIC -shared -o phpstub.so", stdin=r2, stdout=sys.stdout, stderr=sys.stderr, shell=True) | |
fw.write(source) | |
fw2.write(source) | |
os.close(w) | |
os.close(w2) | |
pa.wait() | |
pso.wait() | |
if "__main__" == __name__: | |
mian() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment