/*
* 读取用户的配置文件
*/
void LoadConfig (Bool bMode)
{
FILE *fp;
char buf[PATH_MAX], *pbuf, *pbuf1;
Bool bFromUser = True;// 用以标识配置文件是用户家目录下的,还是从安装目录下拷贝过来的。
int group_idx, i;
Configure *tmpconfig;// 见解说[1]
bIsReloadConfig = bMode;// 这是一个全局变量,定义于“src/tool.c[193]"
pbuf = getenv("HOME");// 从环境变量中获取当前用户家目录的绝对路径
if(!pbuf){
fprintf(stderr, "error: get environment variable HOME\n");
exit(1); // 其实我绝对这儿也可以不退出的。只是处理起来有点烦。
// 需要注意需要直接使用安装目录里面的配置文件,而且不能网配置文件里面写了。直接推出的方法,简单。
}
snprintf(buf, PATH_MAX, "%s/.fcitx/config", pbuf);// 获取配置文件的绝对路径
fp = fopen(buf, "r");// 打开配置文件,如果打不开,说明不存在。就从安装目录里面拷贝一份默认的过来。
if(!fp && errno == ENOENT){ /* $HOME/.fcitx/config does not exist */
snprintf(buf, PATH_MAX, PKGDATADIR "/data/config");
bFromUser = False;
fp = fopen(buf, "r");
if(!fp){
perror("fopen");
exit(1);// 如果安装目录里面也没有配置文件,那就没办法了。只好告诉用户,无法运行了。
}
}
if(!bFromUser) /* create default configure file */
SaveConfig();// 解说[2]
group_idx = -1;//用于标示group的index,在配置文件里面配置是分组的,类似与ini文件的分组。
/* FIXME: 也许应该用另外更恰当的缓冲区长度 */
while(fgets(buf, PATH_MAX, fp)){// 每次最多读入PATH_MAX大小的数据。
i = strlen(buf);
if(buf[i-1] != '\n'){// 这行代码决定了,fcitx的配置文件每行最多是PATH_MAX个字符。
// 如果多余PATH_MAX个字符,就会退出。这也是为什么在前面作者留下那个FIXME的原因。
fprintf(stderr, "error: configure file: line length\n");
exit(1);
}else
buf[i-1] = '\0';// 给字符串做个封口。否则可能会出现问题。
// 比如上一次读入25个字符,这一次仅仅读入2个,那么如果不在这儿加一个空字符的话下面就也会认为是25个字符
pbuf = buf;
while(*pbuf && isspace(*pbuf))// 将pbuf指向第一个非空字符
pbuf++;
if(!*pbuf || *pbuf == '#')// 如果改行是空数据或者是注释(以#开始的语句)
continue;
if(*pbuf == '['){ /* get a group name 组名的格式为“[组名]”*/
pbuf++;
pbuf1 = strchr(pbuf, ']');
if(!pbuf1){
fprintf(stderr, "error: configure file: configure group name\n");
exit(1);
}
// 根据group的名字找到其在全局变量configure_groups中的index
group_idx = -1;
for(i = 0; configure_groups[i].name; i++)
if(strncmp(configure_groups[i].name, pbuf, pbuf1-pbuf) == 0){
group_idx = i;
break;
}
if(group_idx < 0){// 我认为这儿没有必要退出。此处完全可以忽略这个错误,并且在后面也忽略这个组的配置即可。
// 因为这儿退出只会带来一个坏处,那就是扩展性。以后再添加新的组的时候,老版本的程序就无法使用新版本的配置文件了。
// 或者,添加了一个可选扩展,该扩展新添加一个组等等。
// 所以,此处应该给一个警告,而不是退出。
fprintf(stderr, "error: invalid configure group name\n");
exit(1);
}
continue;
}
// pbuf1指向第一个非空字符与=之间的字符
pbuf1 = strchr(pbuf, '=');
if(!pbuf1){// 和前面一样,这儿也应该是一个警告而不应该是提示出错并退出。
fprintf(stderr, "error: configure file: configure entry name\n");
exit(1);
}
// 这儿避免的是那样一种情况,即从文件头到第一个配置项(即类似与“配置名=配置值”的一行字符串)并没有任何分组。
// 也就是防止出现下面的“配置1”和“配置2”
////////////////////////////////////////////
//#文件头
//配置1=123
//配置2=123
//[组名]
//...
//#文件尾
////////////////////////////////////////////
if(group_idx < 0){
fprintf(stderr, "error: configure file: no group name at beginning\n");
exit(1);
}
// 找到该组中的配置项,并将其保存到对应的全局变量里面去
for(tmpconfig = configure_groups[group_idx].configure;
tmpconfig->name; tmpconfig++)
{
if(strncmp(tmpconfig->name, pbuf, pbuf1-pbuf) == 0)
read_configure(tmpconfig, ++pbuf1);
}
}
fclose(fp);
// Ctrl+Space就是fcitx的开关快捷键,此处构造一个结构备用。至于为什么这样做,现在还不清楚。接下去看可能会明白作者的用意。
if (!Trigger_Keys) {
iTriggerKeyCount = 0;
Trigger_Keys = (XIMTriggerKey *) malloc (sizeof (XIMTriggerKey) * (iTriggerKeyCount + 2));
Trigger_Keys[0].keysym = XK_space;
Trigger_Keys[0].modifier = ControlMask;
Trigger_Keys[0].modifier_mask = ControlMask;
Trigger_Keys[1].keysym = 0;
Trigger_Keys[1].modifier = 0;
Trigger_Keys[1].modifier_mask = 0;
}
}
// 相比与LoadConfig来说,LoadProfile实在是简单了很多。因为它没有分组的概念,也不处理注释
void LoadProfile (void)
{
FILE *fp;
char buf[PATH_MAX], *pbuf, *pbuf1;
int i;
Configure *tmpconfig;
// 前将窗口的位置设定为最原始的默认值,接下来如果配置文件有,会从配置文件中读取,如果没有就使用这个了。
iMainWindowX = MAINWND_STARTX; // 主窗口位置X
iMainWindowY = MAINWND_STARTY; // 主窗口位置Y
iInputWindowX = INPUTWND_STARTX; // 输入窗口位置X
iInputWindowY = INPUTWND_STARTY; // 输入窗口位置Y
pbuf = getenv("HOME");
if(!pbuf){
fprintf(stderr, "error: get environment variable HOME\n");
exit(1);
}
snprintf(buf, PATH_MAX, "%s/.fcitx/profile", pbuf);
fp = fopen(buf, "r");
if(!fp){
if(errno == ENOENT)
SaveProfile();
return;
}
/* FIXME: 也许应该用另外更恰当的缓冲区长度 */
while(fgets(buf, PATH_MAX, fp)){
i = strlen(buf);
if(buf[i-1] != '\n'){
fprintf(stderr, "error: profile file: line length\n");
exit(1);
}else
buf[i-1] = '\0';
pbuf = buf;
while(*pbuf && isspace(*pbuf))
pbuf++;
if(!*pbuf || *pbuf == '#')
continue;
pbuf1 = strchr(pbuf, '=');
if(!pbuf1){
fprintf(stderr, "error: profile file: configure entry name\n");
exit(1);
}
for(tmpconfig = profiles; tmpconfig->name; tmpconfig++)
if(strncmp(tmpconfig->name, pbuf, pbuf1-pbuf) == 0)
read_configure(tmpconfig, ++pbuf1);
}
fclose(fp);
// 如果需要保存的话,保存配置。什么时候需要保存呢?
if(bIsNeedSaveConfig){
SaveConfig();
SaveProfile();
}
}
解说:
1. Configure这个结构是作者定义的一个用于操作配置文件的结构。一半来说结构体是用来保存数据的,但是,我这儿却说是用来操作数据的结构体。是不是说错了?没有。这个结构体很有一点c++里面类的意思。即它既又数据,也有数据的处理函数。当然,在这儿就不是成员函数了。而是一个函数指针。
2.
其实,在C语言里面,此类用法是很多的。在linux内核代码的虚拟文件系统里面表现尤其突出。包括我们经常使用FILE都是这类结构体。
至于,这儿的Configure就不做过多的讲解了,它被定义在"src/tool.c"[225]里面,里面有详细的注释。
关于配置文件的写,我就不再解说SaveProfile了,我只简单分析一下SaveConfig函数。
/*
* 保存配置信息
*/
void SaveConfig (void)
{
FILE *fp;
char buf[PATH_MAX], *pbuf;
Configure_group *tmpgroup;
pbuf = getenv("HOME");
if(!pbuf){
fprintf(stderr, "error: get environment variable HOME\n");
exit(1);
}
snprintf(buf, PATH_MAX, "%s/.fcitx", pbuf);
if(mkdir(buf, S_IRWXU) < 0 && errno != EEXIST){
perror("mkdir");
exit(1);
}
snprintf(buf, PATH_MAX, "%s/.fcitx/config", pbuf);
fp = fopen (buf, "w");
if (!fp) {
perror("fopen");
exit(1);
}
// 实际上,写配置文件很简单,就是从全局数组configure_groups里面分别把每个组的配置写入到文件里面去
for(tmpgroup = configure_groups; tmpgroup->name; tmpgroup++){
if(tmpgroup->comment)// 如果存在注释,先写入
fprintf(fp, "# %s\n", tmpgroup->comment);
fprintf(fp, "[%s]\n", tmpgroup->name);// 接下来写入组的名字
write_configures(fp, tmpgroup->configure);// 最后将该组的每个配置项写入到文件中
fprintf(fp, "\n");// 插入一个空行,没有什么用处,仅仅是为了增强配置文件的可读性。
}
fclose(fp);
}
/* 将 configures 中的配置信息写入 fp */
static int write_configures(FILE *fp, Configure *configures)
{
Configure *tc;
for(tc = configures; tc->name; tc++){
if(tc->comment)//如果有注释,先写入
fprintf(fp, "# %s\n", tc->comment);
if(tc->config_rw)// 解说[1]
tc->config_rw(tc, fp, 0);
else{
switch(tc->value_type){
case CONFIG_INTEGER:
generic_config_integer(tc, fp, 0);
break;
case CONFIG_STRING:
generic_config_string(tc, fp, 0);
break;
case CONFIG_COLOR:
generic_config_color(tc, fp, 0);
break;
default:
fprintf(stderr, "error: shouldn't be here\n");// 个人认为这儿应该是一个断言。
//因为,如果出现一个意料之外的类型,说明是程序的BUG,而不是运行时的错误。
exit(1);
}
}
}
return 0;
}
解说:
1. 如果存在特定的读写函数,使用其特定的读写函数写入。为什么这儿不使用通用的函数呢?
因为有些时候,数据写入到配置文件前可能需要进行一些特殊的处理,比如将16进制的颜色值转换成字符串等等。在比如写入一些特定的符号,如快捷键。在这儿,应该就是为了处理快捷键的内部标示与L_CTRL、R_CTRL之类的宏的转换。