一个关于 Pimpl 的小技巧

使用 Pimpl 很多时候是必须的,ABI 兼容的时候基本都要靠它。在 C++ 11 当中,通过 default member initializer 可以实现很多有趣的效果,加上宏的话很多时候可以利用来实现 Metadata 的效果,例如:

#define PROPERTY(NAME) \
    Property NAME{this, #NAME};

class Registry {
    friend class Property;
    public:
        int& getValueByName(const std::string &name) {
            return *values_[name];
        }
    
    protected:
        void registerValue(const std::string name, int* value) {
            values_[name] = value;
        }
        std::unordered_map<std::string, int*> values_;
};


class Property {
public:
    Property(Registry* r, const std::string& name) {
        r->registerValue(name, &value_);
    }
    
    operator int() { return value_; }
    Property& operator=(int v) { value_ = v; return *this; }
    
private:
    int value_;
};

class MyRegistry : public Registry {
public:
    PROPERTY(a);
    PROPERTY(b);
};

int main()
{
    MyRegistry r;
    r.getValueByName("a") = 1;
    r.b = 2;
    return r.a + r.getValueByName("b");
}

但是实际上这还是需要定义新的 member,如果将来有了新的 member 就没法保证 Binary compatibility 了,假设我们要把 MyRegistry 转变成 Pimpl 怎么办呢?简单实现一下的话就会变成这样:

class MyRegistryPrivate {
    Property a{???, "a"};
};

那么 ??? 处传什么才好呢?

你可能会想到把 MyRegistry 的 pointer 传给 Pimpl 不就行了吗?但是不要忘了,我们还依旧希望通过 default member initializer 的形式来定义。传过来的参数之后即使直接去 initialize,也是在 default member initializer 之后才进行了。也就是说这样是不行的:

class MyRegistryPrivate {
    public:
        MyRegistryPrivate(MyRegistry *q) : q_ptr(q) {}

        MyRegistry *q_ptr;
        Property a{q_ptr, "a"};
};

也就是说要保证在更早的时候初始化 q_ptr,可以用一层继承来保证这点。

template
class QPtrInit {
public:
    QPtrInit(T *q) : q_ptr(q) { }
    
protected:
    T* q_ptr;
};

class MyRegistryPrivate;

#define PROPERTY_PRIVATE(NAME) \
    Property NAME{q_ptr, #NAME};

class MyRegistryPrivate : QPtrInit {
    public:
        MyRegistryPrivate(MyRegistry* q) : QPtrInit(q) {}
        
        PROPERTY_PRIVATE(a);
        PROPERTY_PRIVATE(b);
};

完整例子参见:http://cpp.sh/4bhvh

Posted in Linux | Tagged , , | Leave a comment

Shell script for Google TTS on Plasma desktop

My wife has extreme dry eye condition, so she want to avoid looking at the screen as much as possible. She asked me for a solution for Text-to-Speech on linux desktop.

Basically her requirement is to press a key and read out what she selects. I check jovie, but that’s somehow discontinued and the only speech-dispatcher backend available on my distro is espeak, whose voice quality is poor comparing to any modern solution on the desktop.

So I decided to check if there’s any available solution on internet and see if there’s a solution that using Google translate’s TTS service.

So far I found a script from http://elinux.org/RPi_Text_to_Speech_(Speech_Synthesis)#Google_Text_to_Speech

Which I adjusted a little for my usecase:

#!/bin/bash

TEXT=$(xsel)

say() { local IFS=+;/usr/bin/mplayer -really-quiet -noconsolecontrols "http://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&q=$*&tl=en"; }
say $TEXT

I added xsel for reading string from primary selection, which is exactly what she’s asking for. And then I use plasma’s convenient custom global shortcut to bind a key to this script. Then you can enjoy the google tts with a single key press.

Posted in KDE | Tagged | 2 Comments

A new pinyin input method that might be slightly better than sunpinyin

What do I mean when I say “slightly better”?

First of all, thanks to sunpinyin’s open-gram, I can use its data for free. So, on the pinyin side, libime IS using the exact same data from sunpinyin. So what’s the difference?

  1. By default, it learns user input faster (you can do that by tuning sunpinyin option, though).
  2. As a library, it supports multiple dictionary.
  3. It gives you more “candidate phrase” by default.
    I guess I need to explain this a little. So in sunpinyin, many common multiple character words are not represented as a single phrase, but as an ngram. By default, it seems that sunpinyin does not provide enough phrase in such form, while libime will try to provides more ngram phrases as candidates.
  4. It has less bug when you try to modify pinyin in the middle of your input.
  5. Its data file is smaller in size. Thanks to the highly compressed format provided by kenlm.
    $ ls /usr/share/sunpinyin/lm_sc.t3g -lh
    -rw-r–r– 1 root root 38M 3月  23 19:51 /usr/share/sunpinyin/lm_sc.t3g
    $ ls ~/Develop/build/fcitx5/share/libime/sc.lm -lh
    -rw-r–r– 1 saber saber 17M 5月  19 17:59 /home/saber/Develop/build/fcitx5/share/libime/sc.lm
  6. It does not have problem about upgrade dictionary because history stores word directly instead of word id, which may be a problem across different version of data.

I’m using this new pinyin powered by lining daily right now. It just simply “feels” slightly better.

For those who interested: https://github.com/fcitx/libime

Also, FAQ:

– Will I have this with fcitx4 in the future?

No, this is designed to replace the default pinyin and table in fcitx 4 and will only be used for fcitx 5. If you want you can still work on your own solution for fcitx 4.

– How can I use it now?

Build fcitx5 and fcitx5-chinese-addons, you’ll need fcitx5-gtk and fcitx5-qt too, however, I never tried to clean up the optional dependency for it because it’s not the main task right now. So you probably need to have/build everything in dependency. Especially, xcb-imdkit needs to be built. Also please aware that UI of fcitx 5 is still in an extremely poor state. Also you’ll needi to write a config file manually and place it under ~/.config/fcitx5/profile .

[Profile]
# CurrentGroup
CurrentGroup=Default

[Profile/GroupOrder]
0=Default

[Profile/Groups/0]
# Default Input Method
DefaultIM=pinyin
# Layout
Default Layout=us
# Group Name
Name=Default

[Profile/Groups/0/Items/1]
# Layout
Layout=
# Name
Name=pinyin

[Profile/Groups/0/Items/0]
# Layout
Layout=
# Name
Name=keyboard-us

 

Posted in fcitx development | Tagged , | 4 Comments

用 CMake target 来管理依赖库

虽然有越来越多的包提供 CMake config 了,时常还是需要手写一个。但是如果你还活在 cmake 2.8 的时代, 你可能浪费了很多的精力在不必要的地方。

简单来说,如果你还在写 include_directories,link_directories 来给你的依赖添加找头文件和库的目录,那么你大概已经 OUT 了。

比较现代的一种方式是,用 Target 来管理依赖的库的一切。最早我是从 Qt5 的 cmake 那里学到的。简单来说,就是通过 add_library([Library] [TYPE] IMPORTED]) 来把你依赖的库变成一个 cmake target。同时这个 target 就自带了 include_directories 的属性和 library 自身路径。

那么可以来看看一个我写的新 libintl 的例子:https://github.com/fcitx/fcitx5/blob/master/cmake/FindLibIntl.cmake

精髓就是在最后这里:

if(LIBINTL_FOUND AND NOT TARGET LibIntl::LibIntl)
 if (LIBINTL_LIBRARY)
 add_library(LibIntl::LibIntl UNKNOWN IMPORTED)
 set_target_properties(LibIntl::LibIntl PROPERTIES
 IMPORTED_LOCATION "${LIBINTL_LIBRARY}")
 else()
 add_library(LibIntl::LibIntl INTERFACE IMPORTED )
 endif()
 set_target_properties(LibIntl::LibIntl PROPERTIES
 INTERFACE_INCLUDE_DIRECTORIES "${LIBINTL_INCLUDE_DIR}"
 )
endif()

也就是说,如果你在你的项目上 target_link_libraries([YourTarget] LibIntl::LibIntl),它就会自动在编译和链接时找到库和头文件,再也不用写 include_directories(…) 了。

上面还有用到了一个

 add_library(LibIntl::LibIntl INTERFACE IMPORTED )

通过这个来表示这个 library 是纯头文件的库。对于很多模板库,或者 libc 内置的情况来说是很有用的。

对于多 pc 文件的库,例如 cairo 来说,把它用 package + component 的方式来表示有时会很方便。这里可以请出 KDE 写的 extra-cmake-modules。里面自带的 FindXCB.cmake 可以作参考。

这里有一个我写的 Cairo 的实例:https://github.com/fcitx/fcitx5/blob/master/cmake/FindCairo.cmake

find_package(Cairo COMPONENTS Cairo XCB EGL)

使用时可以这样,是不是看起来就很高端大气上档次。实际 link 时就只要 target_link_libraries([Target] Cairo::XCB) 就可以了。

实际上,这样引入的 Tagert 也并不只有这一些属性,甚至可以直接指定依赖时需要使用的 C++ 标准之类。

另一方面,如果你想要在编译时支持 third_party 可以使用系统库,或者使用 bundle 的 source,这个方式就可以在不改变你自己 target 的 CMakeLists.txt 的前提下,更加方便的自动变换。

希望有更多的库使用这种方式直接提供 Find[Package].cmake 呢。

另外实际上,如果你采用 cmake 自动 export 的方式,也可以直接获得这样的效果。暂且挖一个坑,下一次再来说一下 install(EXPORT …) 的故事。

Posted in cmake | Tagged | Leave a comment

讲个有意思的事。

不要奇怪为什么在这个分类下面。这是一个 fcitx 躺枪的故事。

Continue reading

Posted in fcitx development | Leave a comment