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
5
13
2011
6

Cairo 初体验

其实我觊觎 Cairo 很久了。昨天看到这篇文章,就下决心试了下。

简单图形的绘制

这个确实很简单,随便从后文的参考资料处抄点代码过来即可:

static gboolean on_expose_event(GtkWidget *widget,
    GdkEventExpose *event, gpointer data){
  cairo_t *cr;

  cr = gdk_cairo_create(widget->window);

  cairo_set_source_rgb(cr, 0, 0, 0);
  cairo_set_line_width(cr, 1);

  cairo_rectangle(cr, 20, 20, 120, 80);
  cairo_stroke_preserve(cr);
  cairo_set_source_rgb(cr, 1, 1, 1);
  cairo_fill(cr);

  cairo_destroy(cr);

  return FALSE;
}

看函数名基本上知道是在做什么了。我需要一个半透明的矩形,于是查文档,找到cairo_set_source_rgba函数即可。那个 cr,我是折腾到最后才明白它代表了绘制的 context (环境/上下文);至于那个cairo_stroke_preserve,我最终弄明白 Cairo 的绘制方法后才明白的。不管是cairo_rectangle还是我后来用到的cairo_arc,都是“画”了一个路径。之所以要把“画”加上引号,是因为路径本身是看不到的。使用cairo_stroke来描绘路径,使用cairo_fill来填充,而后面带_preserve的版本,是说路径处理完了还留着,下一个操作继续用。我之前犯了个错误,把所有的矩形和圆的路径都画好了才绘制,结果矩形和圆之间有道连线,圆的半径也被画出来了。折腾好久才知道应该在每个路径完成后就绘制。

事件处理

翻了好久的GTK、GDK文档,还是没弄明白该怎么在鼠标拖动时绘制。Google 一下,才看到 Garfileo 的这篇文章,在兴奋之余很有郁闷,Garfileo 以前的 Cairo 相关文章我都收集了,可他为什么后来又换博客了呢。。。。

  gtk_widget_add_events(window, GDK_KEY_PRESS_MASK |
      GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
  g_signal_connect(window, "button-press-event",
      G_CALLBACK(drawing_start), NULL);
  g_signal_connect(window, "button-release-event",
      G_CALLBACK(drawing_finish), NULL);
  g_signal_connect(GTK_OBJECT(window), "motion_notify_event",
      G_CALLBACK(drawing_move), NULL);

就这样就可以了。然后我就开始在每个motion_notify_event发生时画一遍,结果在画的时候图像不停地闪。问了下 gtalk 好友老猫,才知道原来只要在 expose 事件时画就好了,而图像改变后,调用下gtk_widget_queue_draw即可。至于为什么这样图像就不会闪,我至今还不清楚。

右键菜单

我需要在几种不同的选区之间切换。本来用 RadioButton 挺好的,但是我现在只会在 GtkWindow 或者 GtkDialog 里画图。后来就想到了右键菜单。查阅文档后就写出以下代码:

  menu = gtk_menu_new();
  menu_rect = gtk_menu_item_new_with_label("矩形");
  menu_circ = gtk_menu_item_new_with_label("圆形");
  menu_poly = gtk_menu_item_new_with_label("多边形");
  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_rect);
  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_circ);
  /* TODO 绘制多边形 */
  /* gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_poly); */
  g_signal_connect(menu_rect, "activate",
      G_CALLBACK(switch_to_rect), NULL);
  g_signal_connect(menu_circ, "activate",
      G_CALLBACK(switch_to_circ), NULL);
  g_signal_connect(menu_poly, "activate",
      G_CALLBACK(switch_to_poly), NULL);

  ...

  }else if(event->button == 3){
    gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
  }

但是这样点右键时只会显示一个小小的白色小边,就像没有菜单项在里面一样。百思不得其解,于是求助于 Google,最后不知道在哪里看到了,原来还需要对menu调用gtk_widget_show_all。想想也是,对窗口调用gtk_widget_show_all只是把窗口里的东西全部显示出来了,但我这个右键弹出菜单不是在窗口里的呀。

一点题外话

因为要绘制多个图形,本来准备自己写个单链表的,刚写完 node 结构体,突然想到,GLib 里不是有各种数据结构吗?于是再查文档,找到 GSList,愉快地用上了。g_slist_foreach这个函数非常好用。

参考资料

Category: 编程 | Tags: cairo C代码 gtk
4
19
2011
3

Vim的Python3有内存泄漏?继续修正!

给Vim的Python3支持打了个补丁,发到邮件列表上只有Bram表示希望有人来测试就没有下方了。于是,这么久了,这个补丁的内存泄漏问题一直未被发现,直到看到蓝色基因的这篇文章。花了一个下午,发现我原来的补丁不仅没有修正本来就有的内存泄漏,反而雪上加霜,浪费了更多的内存。现在终于弄好了,放在我的陈列室里了,同时还莫名其妙地修正了另一个小问题

既然是内存泄漏,我首先想到的是valgrind这个工具。于是跑了一下:

valgrind --leak-check=full --show-reachable=yes vim

在开启的 Vim 中我 source 了蓝色基因的测试脚本:

lcd %:h
tabedit tmpbuffer
setlocal buftype=nofile
 
python3 << EOF
for i in range(3):
    flines= ['x'*200] * 50000
    vim.command("%s+\\_.*++g")
    for fl in flines:
        vim.current.buffer.append(fl)
    del flines[:]
EOF

整个过程CPU占到100%,而且运行速度极慢,内存消耗也非常多。最后Vim终于按我的指令退出时,valgrind刷屏了大约十几秒钟!而其间我看到除了Python的字样外,还有不少rb的字样。难道Ruby支持也有类似的问题?不过我不管它。重新编译了个只有--enable-python3interp选项的 Vim,这回跑起来快了一些,也没有那么多不相干的内存泄漏了。我也学聪明了点,把信息重定向到文件:

valgrind  --leak-check=full --show-reachable=yes src/vim 2> log

这样可以方便地在log中找“if_py”字符串了。可惜我弄的时候没想到自己会来写博客,所以log文件并没有保存。。。

首先我找到了DoPy3Command这个函数,valgrind说它里面分配的内存没有被释放。这里边的PyUnicode_AsEncodedString这块是我加的:

    /* PyRun_SimpleString expects a UTF-8 string. Wrong encoding may cause
     * SyntaxError (unicode error). */
    cmdstr = PyUnicode_Decode(cmd, strlen(cmd), p_enc, NULL);
    PyRun_SimpleString(PyBytes_AsString(PyUnicode_AsEncodedString(cmdstr, "utf-8", NULL)));

然后我能怎么办呢?当然是查Python的文档了。于是注意到文档上说PyUnicode_AsEncodedString返回的是新的引用。又去看官方教程上的示例,才知道如果一个API返回了新的引用,那么用完后应当手动Py_XDECREF!就像是strdup函数,它内部帮你malloc了,你自己用完后要记着free掉。(Py_XDECREFPy_DECREF的差别是,前者可以传NULL。)

于是就改吧,所有通过PyUnicode_AsEncodedString得到的对象都要Py_XDECREF下。为此,不仅需要临时变量来存储这个对象,更让我郁闷的是,在两个Python版本共有的函数StringToLine中有这样一段代码:

    str = PyString_AsString(bytes);
    len = PyString_Size(bytes);

这里的两个函数/宏我之前是这样定义的:

#define _PyUnicode_AsBytes(obj) PyUnicode_AsEncodedString(obj, p_enc, NULL)
#define PyString_AsString(obj) PyBytes_AsString(_PyUnicode_AsBytes(obj))
#define PyString_Size(obj) PyBytes_GET_SIZE(_PyUnicode_AsBytes(obj))

这下我没辙了,只好又改了if_py_both.hif_python.c文件,加了两个宏:PyString_AsBytesPyString_FreeBytes。它们在 Python2 的代码中什么也不做,但是在 Python3 的代码中用来保存和释放中间对象:

#define PyString_AsBytes(obj) PyUnicode_AsEncodedString(obj, p_enc, NULL);
#define PyString_FreeBytes(obj) Py_XDECREF(bytes)
#define PyString_AsString(obj) PyBytes_AsString(obj)
#define PyString_Size(obj) PyBytes_GET_SIZE(bytes)

有人说,if it ain't broken, don't fix it。可是,虽然问题只出在 Python3 部分,我还是得改 Python2 部分,感觉很不爽。

这样改完,再次反复运行测试代码,结果不遂人愿,依旧泄漏了不少内存。于是继续valgrind,又找到这里:

    static void
BufferDestructor(PyObject *self)
{
    BufferObject *this = (BufferObject *)(self);

    if (this->buf && this->buf != INVALID_BUFFER_VALUE)
	this->buf->b_python3_ref = NULL;
}

然后再次查教程中的示例:

static void
Noddy_dealloc(Noddy* self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

再看看 Python2 部分的代码,在相应的函数里有Py_DECREF,于是把这示例的最后一行给BufferDestructor以及WindowDestructorRangeDestructor加上。再测试,内存不再消耗100多M了,反复source也不会继续增加,于是作出结论:Vim 的 Python3 支持部分没有已知的 bug 了!

做完这一切,我只想说:Vim 这 Python3 支持也太 broken 了吧,中文经常乱码就算了,vim.error不能用我也忍了,竟然还内存泄漏!难道写这个代码的人也是初学Python C API啊?

不过抱怨归抱怨,还是很感谢原作者的,不然我连修正都不可能。不过,patch 弄好也提交了,却一直没人理我,原作者难道是一时兴起才写的、然后就消失了?

最后,补丁现在放到陈列室了。

Category: python | Tags: vim python C代码
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
2
27
2011
9

Wine QQ隐私保护器

鉴于QQ 4 Linux实在太不好用,有些不得不使用QQ的Linux用户使用Wine来运行QQ,我经过努力也成功了,但依旧不用,因为在去年的3Q大战中我们看到腾讯实在难以信任。想想我的ssh密钥、GPG密钥、getmail和msmtp的配置文件等等非常重要的文件QQ皆可轻易扫描并上传……

想过使用另外的低权限用户来wine QQ。然而即使这样,我自己的众多文件QQ依然可以看到,心中还是不安,而且这样作为QQ的重要功能之一——互传文件——将很不方便。于是终于有一天,我开始折腾chroot了。为了不需要总是sudo,我用C写的,可以使用Linux的suid特性,同时也练习下C编程。在man相关系统调用的时候,我还发现了unshare这个调用,竟然可以把挂载的文件系统设置成只在新的挂载命名空间(mount namespace)中可见。这样就可以把为了chroot而mount --bind的项目全部隐藏起来,免得mount输出一大堆不想看到的东西。至于umount嘛,不管了,反正只是mount --bind的。

/* ===================================================================== *
 * chrooted wine QQ
 * ===================================================================== */
#define _GNU_SOURCE
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/mount.h>
#include<linux/limits.h>
#include<sched.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#define ROOT "/tmp/.rwine"
#define PROGRAM "你的QQ的目录"
/* 文件共享目录;若不存在会自动创建 */
#define SHAREDIR "哪个目录作为共享?"
#define BIN "Bin/QQ.exe"
#define ROOTD(dir) ROOT dir
#define MKDIR(dir) mkdir(ROOTD(dir), 0777)
#define MOUNTBIND(dir) mountbind(dir, ROOTD(dir))
int mountbind(const char *source, const char *target);
/* --------------------------------------------------------------------- */
int main(int argc, char **argv){
  int ret;
  ret = unshare(CLONE_NEWNS);
  if(ret){
    perror("unshare");
    exit(1);
  }
  seteuid(getuid());
  ret = mkdir(ROOT, 0777);
  seteuid(0);
  if(ret && errno != EEXIST){
    perror("mkdir new root dir");
    exit(1);
  }

  seteuid(getuid());
  mkdir(SHAREDIR, 0700);
  seteuid(0);
  setgid(0);
  MKDIR("/lib");
  MKDIR("/usr");
  MKDIR("/usr/share");
  MKDIR("/usr/bin");
  MKDIR("/usr/lib");
  MKDIR("/etc");
  MKDIR("/etc/fonts");
  MKDIR("/program");
  MKDIR("/dev");
  MKDIR("/tmp");
  MKDIR("/tmp/share");
  chmod(ROOTD("/tmp"), 01777);

  ret = MOUNTBIND("/lib")
    || MOUNTBIND("/usr/share")
    || MOUNTBIND("/usr/bin")
    || MOUNTBIND("/usr/lib")
    || MOUNTBIND("/etc/fonts")
    || MOUNTBIND("/dev")
    || mountbind(PROGRAM, ROOTD("/program"))
    || mountbind(SHAREDIR, ROOTD("/tmp/share"));
  if(ret){
    perror("mount --bind");
    exit(1);
  }

  char path[PATH_MAX];
  char path2[PATH_MAX];
  char *p;
  strcpy(path, getenv("HOME"));
  strcat(path, "/.wine");
  strcpy(path2, ROOT);
  p = strtok(path, "/");
  seteuid(getuid());
  while(p){
    strcat(path2, "/");
    strcat(path2, p);
    mkdir(path2, 0777);
    p = strtok(NULL, "/");
  }
  seteuid(0);
  /* path changed by strtok */
  strcpy(path, getenv("HOME"));
  strcat(path, "/.wine");
  strcpy(path2, ROOT);
  strcat(path2, path);
  ret = mountbind(path, path2);
  if(ret){
    perror("mount --bind user's wine dir");
    exit(1);
  }
  strcpy(path, getenv("HOME"));
  strcat(path, "/.fonts");
  if(access(path, X_OK) == 0){
    strcpy(path2, ROOT);
    strcat(path2, path);
    seteuid(getuid());
    mkdir(path2, 0777);
    seteuid(0);
    ret = mountbind(path, path2);
    if(ret){
      perror("mount --bind user's font dir");
      exit(1);
    }
  }
  strcpy(path, getenv("HOME"));
  strcat(path, "/.fontconfig");
  if(access(path, X_OK) == 0){
    strcpy(path2, ROOT);
    strcat(path2, path);
    seteuid(getuid());
    mkdir(path2, 0777);
    seteuid(0);
    ret = mountbind(path, path2);
    if(ret){
      perror("mount --bind user's fontconfig dir");
      exit(1);
    }
  }

  ret = chroot(ROOT);
  if(ret){
    perror("chroot");
    exit(1);
  }
  chdir("/tmp");
  ret = setuid(getuid());
  if(ret){
    perror("setuid");
    exit(1);
  }
  execl("/usr/bin/wine", "/usr/bin/wine", "/program/"BIN, NULL);
  /* execl("/usr/bin/wine", "/usr/bin/wine", "notepad", NULL); */
  perror("execl");
  return 1;
}
int mountbind(const char *source, const char *target){
  return mount(source, target, NULL, MS_BIND | MS_NOSUID, NULL);
}
/* ===================================================================== *
 * vim modeline                                                          *
 * vim:se fdm=expr foldexpr=getline(v\:lnum)=~'^\\S.*{'?'>1'\:1:         *
 * ===================================================================== */

程序名为rwine。这里的“r”可不是rsync或者rcp中的“r”,而是“rbash”、“rzsh”、“rvim”中的“r”的意思。在代码的最开头有几个宏是用来配置路径的。suid程序嘛,像这种东西还是硬编码进去好了。因为只是自己使用,所以没有做太多的错误检查之类的,健壮性应该不怎么样。对于这个程序我只要没有安全漏洞就OK了。

另外说个小插曲。在调试这段代码的时候,我发现用户的路径总是挂载得不对,检查了好久,才注意到strtok的声明是

char *strtok(char *str, const char *delim);

第一个参数那里没有const!再仔细看看man文档,果然,它把传进去的参数给改了。

Category: Linux | Tags: C代码 linux QQ 腾讯
2
22
2011
3

Vim7.3 的 Python3 支持修正补丁

Vim 7.3 增加了对 Python3 的支持,但其有不少 bug,从不能正确地向缓冲区中添加中文文本,到 buffer 对象不支持 slice 操作,vim.error 不是 BaseException 的子类而是一个 str,以及各种中文乱码/UnicodeDecodeError,让我这个 Python3 的坚定支持者非常郁闷,于是趁假期把 Vim 好好修理了一番。

此次修正历时两周,涉及 src/if_{py_both.h,python.c,python3.c},共 3 files changed, 308 insertions(+), 265 deletions(-),修正的具体项目为

  • 向缓冲区添加文本时正确处理编码
  • buffer 对象支持 slice 赋值
  • vim.error 不再是字符串
  • py3file 让 Python 检测文件编码
  • 向 Python 传递缓冲区字符串时使用正确的编码(这解决了 gundo 在非 UTF-8 编码时的解码出错)
  • py3 命令输入使用 'encoding' 解码后再以 UTF-8 编码(这解决了在 'encoding' 非 UTF-8 时含中文的 py3 命令的 SyntaxError)
  • 向标准输出写文本时使用正确的编码(这样 print() 之类不会输出乱码)

以上数据要感谢git工具。

目前我在 Ubuntu Linux 10.10 32bit 和 Windows XP SP3 (使用 MinGW 编译)上测试没有问题,有兴趣的请帮忙再测试下。下载地址

另外,附上 gundo 的 Python 2 & 3 兼容版以及使用 Python3 支持的 Python 补全插件 python3complete.vim(放到 ~/.vim/autoload 下)。


PS:Python3.2 发布了,增加了一些很好的新特性,比如argparse模块,str.format_map方法等等。


2011年3月3日更新:给 Vim 编译 Python3.2 支持也有些艰难,参见这篇文章

2011年4月19日更新:今天解决了内存泄漏的问题,补丁已更新。另外,本补丁将在陈列室维护。

2011年6月19日更新:此补丁已被官方采纳。

Category: Vim | Tags: C代码 python vim
10
15
2010
2

wget 默认文件名附加URL查询部分的去除

拿wget下文件,它总是把URL的查询部分(?q=test这种)附加到默认的文件名后,让人十分不爽。查了man手册,也Google过,结论是没有办法解决。虽说拿shell写个脚本在下载完之后把这种尾巴也不难,但总觉得应该从根本上解决问题。于是就hack源码了。

要改的地方其实很好找,在url.c的第1556行附近:

/* Append "?query" to the file name. */
  u_query = u->query && *u->query ? u->query : NULL;
  if (u_query)
    {
      append_char (FN_QUERY_SEP, &fnres);
      append_uri_pathel (u_query, u_query + strlen (u_query), true, &fnres);
    }

把这几行注释掉,然后重新编译就可以了。不需要安装,可以直接覆盖掉系统的wget,但不推荐,因为更新后就没了。我是把它放到~/bin下。这个目录在我的$PATH变量的前面,所以会优先使用这里的wget。

Category: Linux | Tags: wget C代码
9
30
2010
12

colorpicker: 给 Vim 加一个调色板!

作为一名一直喜欢写网页前端的vimmer,自然会使用Vim来编写CSS了。但是,CSS中经常需要调色。虽然相继有一些工具的出现(见可视化的配置Vim(gVim)配色-colorsel.vim),但一直不太理想。编辑CSS的颜色,不仅需要细致的调色,而且需要方便修改,并且支持从屏幕取色是最好不过了!

既然没有现成的,而自己不管怎么说已经入了GTK的门,就自己写了一个简单的工具。本来有个叫gcolor2的调色/取色工具的,可惜它不支持把取到的颜色输出到标准输出,或者任何其它可被其它程序调用的地方(难道这只是某个大牛闲着没事做时写着玩儿的 :-P)。所以,colorpicker与之类似,只有一个标准的GTK调色板。重要的不同之处在于:它可以和其它程序交互。

还是先放个图吧:

就是这样。从图中可以看到,这个标准的调色板除了支持RGB值,还支持HSV,而后者非常适合对色彩进行微调,不像RGB那样只有机器能够理解。左下角有之前颜色和当前颜色的显示(截图时没注意,都是白色。。。)。之前颜色是可以从命令行设置的,这个很有用(请往下看)。也有取色器,在Linux下是可以取到屏幕上任何一点的颜色的。但根据我使用GIMP的经验,好像在Windows上只能取到本程序的颜色。另外还可以看到,图中显示了“透明度”一项。关注Web前端的童鞋都知道,CSS3支持半透明色彩了,所以,本程序也支持输出rgba(79, 0, 159, 0.51)这样的颜色值。

关于程序自身的介绍就到这儿。下面是在Vim里使用的截图:

我需要输入一个颜色值,于是按了我自己定义的Alt-C键,colorpicker启动了。因为是新颜色,加之之前我没使用过,所以“之前颜色”那里显示的是默认的白色。

颜色已经输入了,但我想换一个颜色,怎么办呢?在普通模式下按cac(change a color)就可以了,“之前颜色”会被置为待修改的颜色,方便进行微调。


PS: 在图中可以看到CSS中的颜色值下方背景被高亮成了相应的颜色。这是使用css.vim实现的。

PS2: 这里演示的是修改CSS文件,但实际上是和文件类型无关的。你可以将它用于任何你想用的文件中。

PS3: colorpicker是独立的GTK程序,所以不一定非要和Vim配合工作啦。Emacs什么的一样可以调用,只要有人写出调用代码。

PS4: 程序在哪里下载呢?请移步我的陈列室。Vim调用部分的代码尚未整理,在我的vimrc中,搜索colorpicker就可以了(共两个函数,截图中可以看到函数名。目前(2010年9月30日)它们处于我的vimrc的前50行内)。


2011年4月9日更新

这里 jiazhoulvke 提供了编译好的 Windows 版程序(以及分离出来的 Vim 配置),当然GTK Runtime 环境还是得装,而且还可能遭遇著名的DLL Hell(如果不幸地出现了,那么请把出问题的 DLL 文件从 GTK 的 bin 目录复制到 colorpicker.exe 所在的目录中)。

2014年11月8日更新:

颜色值的高亮显示可以使用 colorizer 插件了哦~

Category: Vim | Tags: gtk css vim C代码
7
9
2010
1

中键关闭GVim的标签

今天在Vim Talks群群地址)上看到闲耘™问到GVim能否像其它很多程序那样使用中键关闭对应的标签。虽然我自己在Vim鼠标用得比较少,但也曾想过这个问题。现在刚刚考试完毕,所以就去试着改了下Vim的源代码。没想到只需要加六行代码呵~~

#ifndef HAVE_GTK2
	    else
		gtk_notebook_set_page(GTK_NOTEBOOK(gui.tabline),
							    clicked_page - 1);
#endif
	}
	/* The following if is added by lilydjwg, to enable closing tab by
	 * middle-clicking. */
	else if (bevent->button == 2)
	{
	    send_tabline_menu_event(clicked_page, (int)(long)TABLINE_MENU_CLOSE);
	    if (gtk_main_level() > 0)
		gtk_main_quit();
	}
    }

    /* We didn't handle the event. */
    return FALSE;
}

以上的代码包含了上下文。把原本不存在的部分添加到gui_gtk_x11.c的3303行附近,然后重新编译即可~

PS: 这个只支持GTK版的GVim,所以不适用于Windows平台。

在此还要感谢Ubuntu大学群的Edward提供帮助。

Category: Vim | Tags: vim gtk C代码

| Theme: Aeros 2.0 by TheBuckmaker.com