10
31
2011
16

在 Linux 下交叉编译带 Python 3 支持的 gvim.exe

今天再一次在 yaourt 的输出中瞥见 mingw 这几个字符,好奇地看了下说明:A C and C++ cross-compilers for building Windows executables on Linux。这个不就是传说中的交叉编译器么?

试试看。以前自己在虚拟机里为 Windows 编译过很多次的 vim,要是能弄到真机下来编译效率应该会高很多。(不,我不是说虚拟机的性能差,而是 Windows 下跑 mingw 这种一堆进程的东西效率差。)

说干就干,几十 M 的 mingw-gcc 及其依赖下好,git archive all|tar x -C ~tmp/vim弄份崭新的 vim 源码,把以前在 win 下用的Make_ming.mak拷过来改改,设置CROSS=yes什么的,然后开始编译!然后很快就出错了,找不到编译器i586-pc-mingw32msvc-gcc。唔,我确实没有那个东东,只有i486-mingw-gcc,去把CROSS_COMPILE这个变量改了就好。然后再 make。这一次的结局是——找不到 Python/Ruby/Lua 的头文件、找不到它们中的符号……好吧,你这交叉编译器当然找不到它们,先注释掉好了。然后重新编译,很顺利地出来个 gvim.exe 了~拿到 Windows 虚拟机里跑一下,一切正常~

好开心,第一次玩交叉编译就成功了。不过还有些小遗憾:没有那些外部脚本语言的支持。这可怎么办呢?我上哪儿找用于交叉编译的库呢?光想是没有用的,还是得尝试。用 Linux 版的库肯定不行,那就试试 Windows 版的。先拿 Python 3 支持测试。把 Windows 下安装的 Python 3 文件夹复制过来,修改下路径,再 make。很正常地,我的第一次尝试总是不成功,好在也不是大问题:Make_ming.mak里写的 include 目录不对:

ifeq ($(CROSS),no)
PYTHON3INC=-I $(PYTHON3)/include
else
PYTHON3INC=-I $(PYTHON3)/win32inc
endif

看看自己的 Python 3 目录,这里的win32inc应该是include。改改就好了 ;-)

嗯,顺利成功了!接下来,把 Windows 版的 Ruby 和 Lua 也弄过来就不用再跑到虚拟机里去编译 Windows 版的 Vim 了!嗯,NSIS 也有 Linux 版,虽然是在 AUR 里。

最后,编译好的 gvim 还是在这里

Category: Vim | Tags: vim windows 交叉编译 编译 linux
10
14
2011
9

通过命名管道进行异步通信

需求是这样子的:一个程序要提供一个IPC接口,接收异步的命令。这个接口应该尽量简单,能像/proc下的文件那样通过写入数据来通信,所以我选中了命名管道。读取命名管道很简单,像普通文件那样打开然后读取就可以了。但这样做的问题是,在没有写者的时候open会阻塞。man 2 open下找到了两个标志位:O_ASYNCO_NONBLOCK。我被排在前面的O_ASYNC骗了,它只是读写时使用信号进行异步操作,open依旧阻塞。继续向后翻,才看到O_NONBLOCK,还特意注明了Neither the open() nor any subsequent operations on the file descriptor which is returned will cause the calling process to wait.

试了试,发现open并不像读写时那样在将阻塞时返回EWOULDBLOCK错误,而是返回了一个可用的文件描述符。既然文件描述符都有了,接下来自然毫无悬念地select了。完整的演示代码如下:

#!/usr/bin/env python3
# vim:fileencoding=utf-8

import os
import time
import select

fd = os.open('test', os.O_NONBLOCK | os.O_RDONLY)
while True:
  if not select.select([fd], [], [], 1)[0]:
    print('waiting...')
  else:
    got = os.read(fd, 1024).decode().rstrip()
    if not got:
      os.close(fd)
      fd = os.open('test', os.O_NONBLOCK | os.O_RDONLY)
    else:
      print('got', got)
Category: Linux | Tags: linux python fifo 异步
9
24
2011
8

通过PyGObject调用GDK截图

Linux 下截个图挺麻烦的。最开始我想学 scrot 使用 Xlib,结果因为看不懂而放弃,转而使用GDK。搜到了TualatriX的这篇《几十行代码构造一个截屏软件》。虽然才不到50行的 C 代码,但我还是觉得有点长。

本来准备像上次的《使用Xtest模拟鼠标点击》一样写成 Python 模块的,后来从 Vayn 那里看到原来可以通过 PyGObject 来调用 GTK 及 GDK 等等(hello world 程序)。于是我也用这种方式完成了截图的代码,才十几行,原理和TualatriX的完全一样。

import mimetypes
from gi.repository import Gdk

def screenshot(filename, rect=None, filetype=None):
  screen = Gdk.Screen.get_default()
  if rect is None:
    rect = (0, 0, screen.width(), screen.height())
  if filetype is None:
    t = mimetypes.guess_type(filename)[0]
    if t is None:
      raise ValueError('cannot guess filetype for filename: %s' % filename)
    filetype = t.split('/')[1]

  rootwin = screen.get_root_window()
  pixbuf = Gdk.pixbuf_get_from_window(rootwin, *rect)
  pixbuf.savev(filename, filetype, (), ())

不过没有找到PyGObject的文档。官方说可以自己从 gir 文件生成,但是那个脚本在最新版的代码中才有,而那个代码也要求Glib非常新,我的 Arch 上都没有那么新,于是作罢。所以用法除了自己按 GDK 的文档猜就是 Google 了。那个savev的参数我都找到mono的文档去了。。。

PyGObject 默认是使用 GTK 3。也可以指定使用 GTK 2:

import gi
gi.require_version("Gdk", "2.0")
gi.require_version("Gtk", "2.0")
from gi.repository import Gdk, Gtk

/usr/lib/girepository-1.0/下还有一些typelib文件,说明这些库都有 GObject Introspection 支持,可以用包括 Python 3 在内的任何其支持的语言访问。不过我调用 xlib 时出错了:

>>> from gi.repository import xlib
>>> d = xlib.open_display()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.2/site-packages/gi/types.py", line 44, in function
    return info.invoke(*args)
glib.GError: Could not locate XOpenDisplay: `XOpenDisplay': python3: undefined symbol: XOpenDisplay

GObject Introspection 这个东西挺好的,除了文档。文档啊,就算不能支持 Python 的 docstring,至少也弄个 HTML 版出来啊,现在只有堆 XML 文件叫我怎么情何以堪啊,现在用 PyGObject 写代码就像在猜谜。。。

最后,代码的 github 链接

Category: python | Tags: gtk linux python
6
26
2011
3

使用 LD_PRELOAD 进行文件读写重定向

缘起

换Arch后,我把首选字体改成了这样:

  <match target="pattern">
    <test qual="any" name="family">
      <string>monospace</string>
    </test>
    <edit name="family" mode="prepend" binding="strong">
      <string>DejaVu Sans Mono</string>
      <string>文泉驿等宽正黑</string>
    </edit>
  </match>
  <match target="pattern">
    <test qual="any" name="family">
      <string>serif</string>
    </test>
    <edit name="family" mode="prepend" binding="strong">
      <string>DejaVu Serif</string>
      <string>文泉驿正黑</string>
    </edit>
  </match>
  <match target="pattern">
    <test qual="any" name="family">
      <string>sans-serif</string>
    </test>
    <edit name="family" mode="prepend" binding="strong">
      <string>DejaVu Sans</string>
      <string>文泉驿正黑</string>
    </edit>
  </match>

也就是说,DejaVu 的字体优先于文泉驿的,因为我觉得 DejaVu 的英文字体比文泉驿的好看。这一般没什么问题,除了某些PDF文件,不知道怎么搞的,不嵌入字体也就算了,字体名也奇奇怪怪弄得 evince 不认识。因为太奇怪,并且 evince 显示得更奇怪,我也不好像 KaiTi_GB2312→KaiTi 这样做字体替换了(其实应该用别名的,但当时不会)。想到一个很简单的办法是,让中文字体拥有更高的优先级。实践证明这样做有效。但是,我不希望为了几个乱码的PDF而更改我的全局字体配置。于是,基于 LD_PRELOAD 的解决方案出来了。

LD_PRELOAD 是什么?

LD_PRELOAD 是 Linux 动态链接器认识的一个环境变量(不知道其他系统是否支持)。我看的资料是这个Fun with LD_PRELOAD。原理很简单,使用自己的函数覆盖掉其它动态链接库的。因为 libc 对系统调用都有一个 wrapper,所以正常的程序都会“被骗”的。proxychains 这个让其它程序使用 socks/https 代理的程序的原理就是这个。

我的 hack

我想对指定程序(evince)重定向~/.fonts.conf的读取,所以准备覆盖open这个系统调用。按照从那个PDF的链接中找到的源码的方式,写出了最初的代码。结果用cat测试就失败了——原来还有个 manpage 没说的open64。继续测试,又发现对vi写文件时不起作用。于是加上了第三个要覆盖的函数creat

程序写好后,觉得光重定向一个文件的读写不好玩,稍稍扩展一下,支持配置就强大了。本来想学那个PDF提到的netjail一样用环境变量的。但又觉得不好设计,而且 C 语言真的写起来太麻烦了。于是想到了用 Lua 来配置。后来还想到用 Python 的,但很可惜,段错误了。

#include<stdarg.h>
#include<dlfcn.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<limits.h>
#include<unistd.h>

#include<lua.h>
#include<lualib.h>
#include<lauxlib.h>

static int lib_initialized = 0;
static int (*orig_open)(const char*, int, mode_t) = 0;
static int (*orig_open64)(const char*, int, mode_t) = 0;
static int (*orig_creat)(const char*, mode_t) = 0;
static lua_State *L = NULL;
void lib_init();

void die(char *fmt, ...) {
  va_list args;
  va_start(args, fmt);
  vfprintf(stderr, fmt, args);
  va_end(args);
  fprintf(stderr, "\n");
  fflush(stderr);
  exit(-1);
}

static char* redirect(const char* file){
  int ret;
  const char *new;
  lua_getglobal(L, "redirect");
  lua_pushstring(L, file);
  ret = lua_pcall(L, 1, 1, 0);
  if(ret){
    fprintf(stderr, "取得重定向文件路径时出错了: %s\n", lua_tostring(L, -1));
    lua_pop(L, 1); /* 错误信息 */
    return (char*)file;
  }else{
    new = lua_tostring(L, -1);
    lua_pop(L, 1); /* 返回值 */
  }
  return (char*)new;
}

int open(const char* file, int flags, mode_t mode) {
  lib_init();
  file = redirect(file);
  return orig_open(file, flags, mode);
}

int open64(const char* file, int flags, mode_t mode) {
  lib_init();
  file = redirect(file);
  return orig_open64(file, flags, mode);
}

int creat(const char* file, mode_t mode) {
  lib_init();
  file = redirect(file);
  return orig_creat(file, mode);
}

void lib_init() {
  void *libhdl;
  char *dlerr;

  if (lib_initialized) return;

  if (!(libhdl=dlopen("libc.so.6", RTLD_LAZY)))
    die("Failed to patch library calls: %s", dlerror());

  orig_open = dlsym(libhdl, "open");
  if ((dlerr=dlerror()) != NULL)
    die("Failed to patch open() library call: %s", dlerr);

  orig_open64 = dlsym(libhdl, "open64");
  if ((dlerr=dlerror()) != NULL)
    die("Failed to patch open64() library call: %s", dlerr);

  orig_creat = dlsym(libhdl, "creat");
  if ((dlerr=dlerror()) != NULL)
    die("Failed to patch creat() library call: %s", dlerr);

  int ret;
  L = luaL_newstate();
  luaL_openlibs(L);
  char config[PATH_MAX];
  strcpy(config, getenv("HOME"));
  strcat(config, "/.openredir.lua");
  ret = luaL_dofile(L, config);
  if(ret){
    die("Error run ~/.openredir.lua");
  }
  lua_getglobal(L, "redirect");
  if(!lua_isfunction(L,-1)){
    die("Error run 'redirect' function in openredir.lua");
  }
  lua_pop(L, 1);

  lib_initialized = 1;
}

Makefile 如下:

CC=gcc
CFLAGS=-g -Wall -I/usr/include/lua5.1
LDFLAGS=-ldl -llua

.PHONY: all clean

all: openredir.so

openredir.so: openredir.o
    gcc -shared $< -o $@ $(LDFLAGS)

clean:
    -rm *.o

下面这个是配置文件,要放到~/.openredir.lua才行。

redirect = function(path)
  -- TODO use absolute and normalized path
  io.stderr:write('打开文件 ' .. path .. '\n')
  if path == '/' then
    return '/etc/issue.tty1'
  elseif path == '/home/lilydjwg/.fonts.conf' then
    return '/home/lilydjwg/tmpfs/fonts.conf'
  else
    return path
  end
end

示例:

>>> LD_PRELOAD=./openredir.so cat /
打开文件 /
Arch Linux \r  (\n) (\l)

不过使用时在LD_PRELOAD中最好使用绝对路径,因为某些程序会chdir()到其它地方去的。

最后吐槽下,Lua 里把路径转成 normalize 过的绝对路径都没有现成的函数,囧。。。


2014年11月23日更新:openredir 已经放在 GitHub 上了。

Category: Linux | Tags: C代码 fontconfig linux Lua
6
21
2011
3

Arch 休眠设置

之前因为更改分区把Ubuntu的休眠给弄坏了,现在来到Arch下近一个月了,才发现原来Arch的休眠也有问题——休眠成功了,但是再启动直接重新开始了。内核参数resume=/dev/disk/by-label/swap也加了。Google 一下,很容易就找到了答案——原来这个也要配置!

很简单,在/etc/mkinitcpio.confHOOKS数组里加上resume,然后重新生成 initramfs 即可:

sudo mkinitcpio -p kernel26

有点郁闷,竟然默认不支持,安装系统或者 pm-utils 时也没提示什么的。我之前还以为是内核的resume参数不支持通过磁盘标签指定呢。又找了下,wiki 里倒是有。但是每安装个软件都查查 wiki 也太累了吧,那样还不如回 Ubuntu 呢。

Category: Linux | Tags: arch linux
5
6
2011
9

login shell 和 non-login shell 不同造成的问题

上篇说到,我在Arch下的tmux的部分环境变量有问题。于是接下来我开始调查原因。最后终于真相大白。不过在揭露真相前,先详细说说问题是什么。

自从使用zsh以后,我在Ubuntu下发现我在~/.profile中设置PATH变量的代码在tty下没有起作用。但在~/.zshrc中设置又不行,因为图形界面登录时不会读取~/.zshrc。source 它也不行,因为可能导致双重设置(在一段时间里,我总是很奇怪地发现命令补全时某些命令会出现两次。。。)。于是,最后我的方案是这样的(箭头表示 source):

.profile --> .zsh/zshrc.env   <-+
.zshrc --> ZSHRC_ENV set? --No--+

这个方案在Ubuntu下一直工作良好。但在Arch+tmux下就出问题了。在tmux中的zsh启动前,ZSHRC_ENV已经设置,于是~/.zsh/zshrc.env没有被 source,于是$PATH设置得不对了。。。

在查阅tmux N次之后,我想,可能是某个启动文件覆盖了我自己的 PATH 变量的设置。于是打开 zsh 的文档,翻到这里:

5.1 Startup/Shutdown Files

Commands are first read from /etc/zsh/zshenv; this cannot be overridden. Subsequent be- haviour is modified by the RCS and GLOBAL_RCS options; the former affects all startup files, while the second only affects global startup files (those shown here with an path starting with a /). If one of the options is unset at any point, any subsequent startup file(s) of the corresponding type will not be read. It is also possible for a file in $ZDOTDIR to re-enable GLOBAL_RCS. Both RCS and GLOBAL_RCS are set by default.

Commands are then read from $ZDOTDIR/.zshenv. If the shell is a login shell, commands are read from /etc/zsh/zprofile and then $ZDOTDIR/.zprofile. Then, if the shell is interactive, commands are read from /etc/zsh/zshrc and then $ZDOTDIR/.zshrc. Finally, if the shell is a login shell, /etc/zsh/zlogin and $ZDOTDIR/.zlogin are read.

于是发现这一切的根源在于tmux里启动的是login shell!Arch的/etc/profile中重置了$PATH/etc/profile.d/locale.sh中重置了$LANG,所以造成我的tmux下的zsh环境变量不对的问题。于是我把设置移回了~/.profile中,然后将软链接~/.zprofile指向它。locale.shpacman不知道是什么包的,所以我就把它改成了:

[ -z "$LANG" ] && export LANG=en_US.UTF-8

至此,tmux部分的问题终于解决了!

Category: Linux | Tags: arch linux zsh tmux
4
7
2011
9

带补丁gvim 7.3 Arch软件包下载

准备向 Arch 迁移了,于是编译了个GVIM。之所以要自己编译,当然是要打非官方补丁了。

主要的 bug 修正为:

其中第三条只在 Arch 上出现,据说是某个库的 bug。后来折腾了段时间,发现如果 gvim 不 fork,或者 fork 后父进程生存的时间长一点点,就不会错误地出现这个提示(但是真正使用超级用户权限时也没有这个提示,不知道是否属正常情况)。


2011年7月14日更新:最新版下载地址

Category: Vim | Tags: arch C代码 linux vim
4
1
2011
3

五发行版联合发布新的发行版——The Canterbury Distribution

今天收到 arch-announce 邮件列表的消息,Arch Linux, Debian, Gentoo, Grml and openSUSE 联合发布 The Canterbury Distribution 了~~不信?那访问它们的主页试试:

PS: Arch 做了点小手脚哦~


Happy April Fool's Day!

Category: Linux | Tags: linux joke
3
19
2011
10

Ubuntu下折腾分区后休眠不能唤醒问题的解决

我自以为把 Linux 的分区相关的东西折腾得比较熟了,所以就大胆地在本机上折腾分区,结果有一天就发现,在我在 /etc/fstab 里把 swap 区从UUID指定改成用LABEL指定之后,休眠可以成功,但唤醒失败。在开机后应当从 swap 分区恢复的时候,出现错误,然后就直接启动了,其间还毁掉了 swap 分区的内容。这么试过了两次,都失败了,于是我带着迷惑,再也没有试过了。

前天在群里看到这个链接,终于恍然大悟——原来还有个配置文件/etc/initramfs-tools/conf.d/resume!它指明了唤醒时从哪个分区恢复。当初改 swap 后我只改了fstab但没有改它,所以可以正常使用却不能唤醒。不过重新生成 initrd 时为什么没有自动更新 resume 文件呢?而且休眠时系统也不检查下resume文件,郁闷。不知道休眠时能不能指定休眠到哪个分区上呢?

resume文件的内容相当简单,就一行,像这样RESUME=LABEL=swapRESUME=后面的格式和 fstab 的第一栏一样。改了后还要sudo update-initramfs -u更新 initrd。

另外,唤醒不成功的时候我曾尝试过 s2disk 这个工具,但昨天发现,虽然它在休眠时有进度显示,可是在唤醒的时候失败了——当进度达到 100% 时就没动作了,我的桌面也就没能回来……

Category: Linux | Tags: linux ubuntu
3
16
2011
22

关于“Linux”被翻译为“你牛叉”等的一些想法

昨天,我就在我所建立的vim-cn Gtalk群上看到了在Debian官方简体中文首页将“Debian”翻译为“蝶变”、“Linux”翻译为“你牛叉”,当时还以为是某人的恶作剧。今天,又见到这篇文章,才知道真相。

最开始读的时候,看到大量的翻译破坏,我想到是不是使用的机器翻译,随后意识到不可能。又以为是Linux反对者的发泄。但我还是错了。

做这件事的人是“沈卓斌”,在多个 Wiki 上的 ID 是“jobinson99”。他除了做这些莫名其妙的翻译更改外,也做了一些妥当的翻译,让恢复变得困难起来。我在ArchLinux上的几个页面改掉了他的一些翻译,更多的翻译还得依靠Google来找出了。在改的过程中我发现部分页面有相互复制的情况。不过我没精力管了。至于维基百科,其中文社区比较强大,针对其的破坏已经被恢复。其他的Debian啊、FreeBSD啊我就没心思去管了。

有些人可能对此不太在意,但我很早就读了中文维基百科的一些方针指引之类的,很快便认定这是“破坏行为“。尽管他的这些翻译看起来是善意的,但是其行为却不会被接受。首先不说他的翻译是否准确恰当,首先一点就是——没有共识。我认为,再好的翻译,只有极少数人知道,它也不应当被作为通用或者正式的名称,不然,就像使用世界语一样,大部分人根本就不知道你在说什么,更别说搜索引擎了。哪天你有某个Linux问题,Google数日未解决,却蓦然发现,原来写有解决方案的Wiki中使用的是“你牛叉”而导致你没有找到,岂不郁闷!更何况,这些翻译多似恶搞,不是所有人都会接受的,我最开始就以为是反对者来着。

我很不明白,各开源项目的维基上尚有大量资料需要翻译,中文 man 手册既少又旧以至于我都不敢用了,wget 的中文翻译错误虽然在 Ubuntu 下已经更正了,但在 ArchLinux 下依旧存在。还有这么多可以做的,他却以为无聊的翻译可以推广开源?新手安装 Linux,谁不需要安装指导?遇到问题了,为什么可能的解决办法都是用英文写的?命令不会用,man 一下,为什么没有中文翻译?很多新手在尝试Linux时都遇到各种困难而放弃,很多老鸟都在遇到问题时苦苦挣扎,我多么希望所有的开源资料能有中文翻译!很多人说Linux难用。是啊,不会英语又没人指导就根本无法使用的系统怎么会好用呢?

至于jobinson99,我想说,如果你真的蛋疼得不行的话,就把翻译放伪基百科上去啊。


附:我在 ArchWiki 上发现的不妥翻译列表:

%s/开天辟地你牛叉/LFS/ge
%s/你牛叉龙骨/ArchLinux/ge
%s/龙骨/Arch/ge
%s/你牛叉/Linux/ge
%s/共努/GNU/ge
%s/荟萃/Wiki/ge
%s/有奔头/Ubuntu/ge
%s/蝶变/Debian/ge
%s/合成/编译/gce
%s/优盘/U盘/gce

 

Category: Linux | Tags: linux 中文支持

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com