12
21
2011
5

Rcode.vim 2.0 发布

Rcode, 即「Run Code」的缩写。Vim 拥有多种脚本语言的接口。有时候需要对文本进行较复杂的处理,但是在单行的命令行里能写的代码功能有限。你可能会立即想到,先把代码写到一个文件里再加载。没错,这就是 Rcode.vim 为你做的。2.0 版本在原来简单的「编写」->「执行」基础上添加了代码保存功能,可供重新载入(并加以修改)。此特性是由易名建议的。

命令和键映射:
:Rcode		启动 Rcode,需要一个参数指明语言,如 vim、
		awk 等。使用 <C-D> 可以查看所有当前被
		支持的语言。会开启一个新的缓冲区,请把你的代
		码写在里边。此命令可以接受一个范围。

:RcLoad {name}	加载之前保存的代码。
		「name」是「{lang}/{filename}」的形式,这和
		保存的参数有些不同,因为脚本需要知道代码的语种。
		此命令可以接受一个范围。

:RcSelect	列出所有已保存的代码,使用数字来选择。

在 Rcode 的缓冲区里:

<C-CR>
:Run		在你启动 Rcode 时的缓冲区上执行代码。
:Save {name}	保存代码以在日后可使用「:RcLoad」命令载入。

别名:
在 Python 中,「v」为「vim」模块,「b」为当前的缓冲区对象。
在 Lua 中,「b」为当前的缓冲区对象。 

设置:
g:Rcode_after	执行代码后的行为。
		0 什么也不做,1 关闭该窗口,2 关闭的同时也不
		要记住代码,不然下次使用「:Rcode」命令时会显
		示此代码。默认值为 1。

g:Rcode_snippet_path	代码保存的路径。
			默认值是 "$HOME/.vim/rcode"。

下载地址

Category: Vim | Tags: vim
12
18
2011
7

在 Arch 上使用 PulseAudio

一直不怎么懂关于音频的配置,所以一直没管这方面,直到遇到小麻烦。我把gnome-volumn-control干掉之后,发现音量无法调到 100% 以上了,某些视频音量太小听不清。这才第一次正视音频配置。

其实也说不上有多么「正视」,因为我只是在 ArchWiki 上找了几条命令执行了下而已。

安装了以下软件包:

  • pulseaudio-alsa, 就一个配置文件,用处不明
  • pamixer-git. 命令行调节音量用的,Awesome 音量 widget 改用这个了
  • pavucontrol, 图形界面的音量调节工具。更新后的 Awesome 音量 widget 上点右键运行它,可以针对不同的程序进行调节

另外,在 mplayer 的配置文件中加了ao=pulse这行。

就这些了。

Category: Linux | Tags: Arch linux awesome 音频
12
5
2011
13

Awesome 调节音量不再依赖 GNOME

之前一直在用 gnome-sound-applet 来调节音量。今天终于脱离了它。

GNOME 越来越臃肿了。今天系统出了点小问题,查看日志时再次看到 dbus 报怨 NetworkManager 没有运行的错误。我是直接用的 ArchLinux 的network服务连网的,根本不需要 NetworkManager 掺和,可是自己要用 Empathy,而它奇迹般地依赖 NetworkManager。。。虽然试了下强制卸载,最后只成功pacman -Rdd几个其它的包,没能在保证 Empathy 能用的前提下干掉 NetworkManager,心中多有不甘。于是开始打 gnome-sound-applet 的主意。

gnome-sound-applet 这东西是 gnome-control-center 的一部分,其依赖了 15M 左右的奇怪 GNOME 组件。这个 applet 也很鸡肋,本来是显示和调节音量用的,结果黑黑的图标在我的灰色 Awesome 面板上并不容易找到,花了良久才习惯。另外就是,该图标本身不提供任何信息,查看音量时需要把鼠标悬停过去,心中多有不爽。好在今天这些问题一并解决了。

参考来源是 Awesome Wiki 的这个页面,不过这里的代码太老了,不能用,自己参照rc.lua的其它部分以及 wiki 和 reference 修改之后才能用。最终的效果是这样子的:

Awesome widgets 截图

你应该很容易能猜到最右边的就是音量控制了,因为它前边有个八分音符符号“𝅘𝅥𝅮”。鼠标操作很简单:单击切换静音,上下滚动调节音量。静音时百分号会被红色的“M”取代。

贴代码:

-- {{{2 Volume Control
volume_cardid  = 0
volume_channel = "Master"
function volume (mode, widget)
  if mode == "update" then
    local fd = io.popen("amixer -c " .. volume_cardid .. " -- sget " .. volume_channel)
    local status = fd:read("*all")
    fd:close()

    local volume = string.match(status, "(%d?%d?%d)%%")
    volume = string.format("% 3d", volume)

    status = string.match(status, "%[(o[^%]]*)%]")

    if string.find(status, "on", 1, true) then
      volume = '𝅘𝅥𝅮' .. volume .. "%"
    else
      volume = '𝅘𝅥𝅮' .. volume .. '<span color="red">M</span>'
    end
    widget.text = volume
  elseif mode == "up" then
    io.popen("amixer -q -c " .. volume_cardid .. " sset " .. volume_channel .. " 5%+"):read("*all")
    volume("update", widget)
  elseif mode == "down" then
    io.popen("amixer -q -c " .. volume_cardid .. " sset " .. volume_channel .. " 5%-"):read("*all")
    volume("update", widget)
  else
    io.popen("amixer -c " .. volume_cardid .. " sset " .. volume_channel .. " toggle"):read("*all")
    volume("update", widget)
  end
end
volume_clock = timer({ timeout = 10 })
volume_clock:add_signal("timeout", function () volume("update", tb_volume) end)
volume_clock:start()

tb_volume = widget({ type = "textbox", name = "tb_volume", align = "right" })
tb_volume.width = 35
tb_volume:buttons(awful.util.table.join(
  awful.button({ }, 4, function () volume("up", tb_volume) end),
  awful.button({ }, 5, function () volume("down", tb_volume) end),
  awful.button({ }, 1, function () volume("mute", tb_volume) end)
))
volume("update", tb_volume)

记得把 tb_volume 加到 wibox 里去。

这里是我的整个 Awesome 配置。

2013年3月11日更新:Awesome 3.5 版本语法变化较大,请到我的 github 上查看相关代码。

Category: Linux | Tags: arch awesome gnome Lua linux
11
28
2011
7

弄了个支持 readline 的 MongoDB shell

不知道怎么搞的,新版 MongoDB 自带的 mongo shell 现在不支持 readline,而且使用一个极简到工作不正常的 linenoise。编译时加上 readline 也没用。虽然内建了简单的历史记录和补全,可是历史记录不能搜索,补全只能像 Vim 命令行默认的那样一个一个切换不能像 bash/zsh 那样全部列出来。这些还不是最令人郁闷的。最让我受不了的问题和十年前的 DOS 版 WPS 里遇到的差不多——当年的 WPS 里删汉字一次只删一半,现在的 mongo shell 里删汉字一次只删三分之一!而且光标定位是错的,按字节算的!

于是乎拿 Python 写了一个 shell。不愧是 Python,内置的东西真不错,不到100行就写好了。不过用到了自己另外的库函数,另外用到了自己的 colorless 程序来高亮显示查询结果。如果不想要 pygments 这个依赖的话,可以用 less 程序代替。以下贴个无高亮版的,高亮版的见 github

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

import sys
import os
from pprint import pprint
import subprocess

from pymongo import Connection
import pymongo.cursor

# 这个模块位于 github 上的 winterpy 仓库的 pylib/cli.py
from cli import repl

import locale
locale.setlocale(locale.LC_ALL, '')
del locale

host = 'localhost'
port = 27017
db = 'test'

def displayfunc(value):
  if isinstance(value, pymongo.cursor.Cursor):
    p = subprocess.Popen(['less', '-RFX'], stdin=subprocess.PIPE,
                        universal_newlines=True)
    pprint(list(value), stream=p.stdin)
    p.stdin.close()
    p.wait()
  else:
    pprint(value)

def main():
  global db
  conn = Connection(host=host, port=port)
  db = conn[db]

  v = globals().copy()
  v.update(locals())
  del v['repl'], v['argv'], v['main'], v['v'], v['host'], v['port']
  del v['displayfunc'], v['subprocess']
  del v['__name__'], v['__cached__'], v['__doc__'], v['__file__'], v['__package__']
  sys.displayhook = displayfunc

  repl(
    v, os.path.expanduser('~/.mongo_history'),
    banner = 'Python MongoDB console',
  )

if __name__ == '__main__':
  argv = sys.argv
  if len(argv) == 2:
    if '/' in argv[1]:
      host, db = argv[1].split('/', 1)
    if ':' in host:
      host, port = host.split(':', 1)
  elif len(argv) == 1:
    pass
  else:
    sys.exit('argument error')

  main()
Category: python | Tags: python MongoDB linenoise readline
11
26
2011
8

给博客加入 Google +1 按钮

本来我是懒得去做这种东西的,可鉴于以下几个原因,还是弄了一个。

  • Mike 也 +1 了
  • 代码很简单
  • 方便分享到 Google Plus
  • 反正从国内访问本博客也不容易,就不在乎这个 +1 按钮很可能出不来了

代码如下,真的很简单:

<script type="text/javascript" src="https://apis.google.com/js/plusone.js">{lang: 'zh-CN'}</script>
<g:plusone></g:plusone>

我把它加到了每篇文章的末尾,因为我不认为没看完文章的读者会需要用到它。看不到按钮的读者请自行解决对 Google HTTPS 的访问问题。

Category: 未分类 | Tags: Google
11
9
2011
14

在 Arch 上启用 NTP 服务对时

一日即将按算好的时间外出。将系统挂起,出门时检查下手机,却郁闷地发现电脑时间又慢了!十几分钟啊。。。。

我的手机是和移动运营商的网络时间同步的,因此不出事故它是可靠的。看来电脑的时钟也应该同步下了,不然过段时间要手动调下。

毫不犹豫地在ArchWiki里搜索NTP,按wiki进行操作。这wiki写了那么长,其实只要对时的话操作很简单。

  1. sudo pacman -S ntp安装
  2. sudo rc.d start ntpd启动 daemon
  3. vim一下/etc/rc.conf,加入开机启动项中,同时把hwclock干掉

就这么三步。至于同步的时间服务器,默认的pool.ntp.org就挺好的。ntpd 启动后系统时间并没有马上改变,而是过了几分钟才在我不注意的时候悄悄同步准确了。

Category: Linux | Tags: linux arch ntp
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
23
2011
3

使用 fontconfig 进行字体查询

Vayn想知道如何判断一个字体是否支持中文,我也对字体的种种特性好奇,于是我再一次淹没在文档之中。先是翻了半天Pango的文档,各种字体相关的函数,还找到个pango_has_char函数。不过我没能弄明白怎么它需要的参数类型PangoFcFont怎么弄。后来查到这个函数需要底层支持,于是我直接找到 fontconfig 去了。

fontconfig 的文档不怎么样,虽然后来发现它提供了manpages、PDF、HTML、TXT等格式,但我依旧没能从文档中弄明白如何得到一个字体的信息。看了 fc-query 的源代码才知道,原来FcPattern既用来作输入,也用来作输出。查询字体时它是查询条件,而返回时它就是字体信息。

/* ===================================================================== *
 *  判断某个字符是否存在于指定的字体(文件)中
 * ===================================================================== */
#include<stdio.h>
#include<string.h>
#include<fontconfig/fontconfig.h>
/* --------------------------------------------------------------------- */
int main(int argc, char **argv){
  int ret = 0;
  FcChar8* file = (FcChar8*)"/home/lilydjwg/.fonts/迷你简启体.ttf";
  FcPattern* pat;
  FcCharSet* cs;
  FcChar32 ch;
  int count;

  FcBlanks* blanks = FcConfigGetBlanks(NULL);
  pat = FcFreeTypeQuery((FcChar8 *)file, 0, blanks, &count);

  if(FcPatternGetCharSet(pat, FC_CHARSET, 0, &cs) != FcResultMatch){
    fprintf(stderr, "no match\n");
    ret = -1;
    goto cleanup;
  }

  FcUtf8ToUcs4((FcChar8*)"简", &ch, 3);
  if(FcCharSetHasChar(cs, ch)){
    puts("Yes");
  }else{
    puts("No");
  }

cleanup:
  FcPatternDestroy(pat);
  return ret;
}
/* ===================================================================== *
 * vim modeline                                                          *
 * vim:se fdm=expr foldexpr=getline(v\:lnum)=~'^\\S.*{'?'>1'\:1:         *
 * ===================================================================== */
/* ===================================================================== *
 *  判断某个字符是否存在于指定的字体(条件匹配)中
 * ===================================================================== */
#include<stdio.h>
#include<fontconfig/fontconfig.h>
/* --------------------------------------------------------------------- */
int main(int argc, char **argv){
  FcFontSet* fs = NULL;
  FcPattern* pat = NULL;
  FcObjectSet* os = NULL;

  FcChar8* strpat = (FcChar8*)":lang=zh";
  pat = FcNameParse(strpat);
  os = FcObjectSetBuild(FC_FAMILY, FC_CHARSET, FC_FILE, (char *)0);
  fs = FcFontList(0, pat, os);
  if(os)
    FcObjectSetDestroy(os);
  os = NULL;

  FcPatternDestroy(pat);
  pat = NULL;

  if(!fs || fs->nfont <= 0)
    goto nofont;

  FcChar8 *family;
  FcChar8 *file;
  FcCharSet* cs;
  FcChar32 ch;
  FcUtf8ToUcs4((FcChar8*)"这", &ch, 3);
  int i;
  for(i=0; i<fs->nfont; i++){
    if(FcPatternGetCharSet(fs->fonts[i], FC_CHARSET, 0, &cs) != FcResultMatch){
      fprintf(stderr, "no match\n");
      FcPatternPrint(fs->fonts[i]);
      goto nofont;
    }
    if(FcPatternGetString(fs->fonts[i], FC_FAMILY, 1, &family) != FcResultMatch)
      if(FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, &family) != FcResultMatch)
	goto nofont;
    printf("[%d] %s ", i, (char *)family);
    if(FcPatternGetString(fs->fonts[i], FC_FILE, 0, &file) != FcResultMatch)
      goto nofont;
    printf("(%s): ", (char *)file);
    if(FcCharSetHasChar(cs, ch)){
      puts("Yes");
    }else{
      puts("No");
    }
  }

  FcFontSetDestroy(fs);

  return 0;

nofont:
  return 1;
}
/* ===================================================================== */
Category: Linux | Tags: fontconfig C代码
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 异步
10
13
2011
2

GM 脚本:在 Chito 后台评论列表中显示评论者的地址位置

GreaseMonkey 代码如下:

// ==UserScript==
// @name           is-programmer 后台评论地理位置显示
// @namespace      http://lilydjwg.is-programmer.com/
// @description    通过 JSONP 查询 IP 地址对应的地理位置并显示
// @include        http://*.is-programmer.com/admin/comments*
// @include        http://*.is-programmer.com/admin/messages*
// ==/UserScript==

var qurl = function(ips){
  return 'http://localhost:2000/queryip?q=' + ips.join(',') + '&cb=?';
};

var letsJQuery = function(){
  var ip_header = document.evaluate('//th[@class="helpHed" and text()="IP"]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);
  $(ip_header).after('<th class="helpHed">地址</th>');
  var ip_cells = document.getElementsByClassName('comment_ip_col');
  var ips = [];
  var i;
  for(i=0, len=ip_cells.length; i<len; i++){
    ips.push(ip_cells[i].textContent);
  }
  $.getJSON(qurl(ips), function(data){
    var ans = data.ans;
    for(i=0, len=ip_cells.length; i<len; i++){
      $(ip_cells[i]).after('<td class="comment_addr_col">'+ans[i]+'</td>');
    }
  });
};

function GM_wait(){
  if(typeof unsafeWindow.jQuery == 'undefined') {
    window.setTimeout(GM_wait, 500);
  }else{
    $ = unsafeWindow.jQuery;
    letsJQuery();
  }
}

GM_wait();

光有这个脚本是不够的,因为没有 IP 地址数据库。我不想像这样用 chrome 权限调用子进程之类的手段,而是从本地 HTTP server 取得数据,这样以后可以方便地扩展。HTTP server 使用 Python 的 tornado 框架写成,名字是“Web Service Provider”的缩写:

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

from subprocess import getoutput
from functools import lru_cache
import json

import tornado.web
import tornado.httpserver
from tornado.options import define, options

@lru_cache()
def lookupip(ip):
  return getoutput('cip ' + ip).replace('CZ88.NET', '').strip() or '-'

class IPHandler(tornado.web.RequestHandler):
  def get(self):
    q = self.get_argument('q').split(',')
    addr = []
    for ip in q:
      a = lookupip(ip)
      if 'illegal' in a:
        a = '(错误)'
      elif '\n' in a:
        a = ''
      addr.append(a)

    ans = {
      'ans': addr,
    }
    cb = self.get_argument('cb', None)
    if cb:
      self.set_header('Content-Type', 'text/plain; charset=utf-8')
      self.write('%s(%s)' % (cb, json.dumps(ans, ensure_ascii=False)))
    else:
      self.write(ans)

def main():
  define("port", default=2000, help="run on the given port", type=int)

  tornado.options.parse_command_line()
  application = tornado.web.Application([
    (r'/queryip$', IPHandler),
  ],
    debug=True,
  )
  application.listen(options.port)
  tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
  try:
    main()
  except KeyboardInterrupt:
    pass

用的是 Python 3.2,我很喜欢它的lru_cache装饰器。

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com