自制快速调节鼠标灵敏度的小玩意——SimpMouse


介绍:

这是我最近弄的小玩意,可以识别连接的鼠标,并分别调节每个鼠标的“减速系数”(Constant Deceleration)。

代码托管在Github上:
https://github.com/ukyoi/simpmouse (点此进入项目页面)
欢迎fork和贡献(虽然我觉得应该没什么人fork)。

功能上,选中要调节的鼠标,然后拖动滑杆或输入数值,就可以改变鼠标灵敏度。由于减速系数取值是1到正无穷(应该是正无穷吧……),所以滑杆那里我取了个倒数,左端对应数值为16。直接输入的话则可以接受任何有效的数值。

另外,虽然有Refresh、Apply和Cancel三个按钮,不过这些都是半年前构想中的功能,现在统统用不了,当然更无什么记忆功能。总之只是个很简陋的半成品。

主要用到的东西:Qt做界面、C++、Qt做基本功能(使用qmake进行构建)。
通过运行 xinput list 和 xinput list-props 获取鼠标信息,并用 xinput set-prop 写入Constant Deceleration数值。

关于这个程序的一些废话:

最初想法源于半年前跟人联网用wine玩星际。KDE的鼠标调节功能比较残,指针加速那里即使调到最低,我的鼠标也还是太灵敏(xfce的鼠标调节就很好,可以每个鼠标独立调整,项目也多)。网上查到用xinput可以对鼠标行为进行精细调节,我就打算写个方便的图形工具来用。结果一下拖到现在,星际自然是早就不打了,程序也变成了练手(自娱自乐)之作。至于程序名……由于这个程序对多数人来说实在是没啥大用,请原谅我没在上面花什么心思。

我本以为如此小程序,不会有什么代码量,然而最终代码量却出乎我的意料。当然回报也是有的,我的最大收获大概在于对如何组织程序的结构有了一些经验。我之前只是看过一些编程语言的教材和简单的算法,而对哪怕一个很小的程序应该如何设计和组织则全无经验。通过边写这个程序边进行重构,我对代码该怎么划分、怎么组织都有了一些粗浅的了解。虽然程序本身很小,但我自认为带来的收获还是可观的吧。

Advertisements

C/C++ 包含顺序引发的错误一例


这个错误是我最近在写一个Qt小程序的时候出现的,觉得有一定价值,打算写出来。由于我水平实在太弱,故若高手们看了之后觉得浪费了自己的生命,还请轻拍。

假如我的程序除了main.cpp之外还有3个.h/.cpp组,这里用A.h、B.h、C.h代替:

// main.c
#include "A.h";
// Do something...
return 0;
// A.h
#ifndef A_H
#define A_H

#include "B.h";
#include "C.h";

#endif
// B.h
#ifndef B_H
#define B_H

class A_Class;

#endif
// C.h
#ifndef C_H
#define C_H

#include "A.h";
void a_function(A_Class an_arg);

#endif

可以看出,C.h的一个函数中要用到B.h中声明的一个叫做A_Class的类。由于A.h包含了C.h和B.h,而C.h又包含了A.h,因此看上去C.h可以通过A.h包含B.h,从而用上B.h中声明的类。

之后通过qmake等等自动写makefile的工具生成makefile,进行编译。然而在编译的时候,编译器却会给出错误,说A_Class没有被声明。而如果把A.h中包含的顺序调换一下,先包含B.h,再包含C.h,编译就能够通过。

问题主要就出在A.h和C.h的相互包含上。A.h先包含了C.h,根据生成的makefile,编译器会先开始处理C.h,而在C.h被处理完毕之前,B.h是不会被处理的,因此编译器会说找不到B.h中声明过的类。而如果把A.h中包含的顺序调换,在处理C.h之前,B.h已经被处理好了,就不会出现找不到声明的情况。

因此为了避免这类问题,最好把头文件间的关系理清,不要出现这种“互相包含”的情况。如果非要用到彼此的东西,可以使用前向声明(但这种方法实际操作起来是很麻烦的)。另外,既然有了#ifndef的保护,凡是要用到的东西统统在头文件里包含一遍,这样也可以确保所有需要用到的东西在用到之前都是准备好的。

Fcitx皮肤查看器


虽说私很懒,不过不忙的时候还是会捣鼓一些东西的,这Fcitx皮肤查看器就是私最近主要在弄的一个。工程方面基本是CSSlayer仁兄打的底子。
配置读取部分是直接调用Fcitx的API,且貌似Fcitx4.1的皮肤配置有所更改,因此应该至少要求Fcitx版本号高于4.1。
虽说只有最基本的功能,输入框还没有写好,边栏还只是把东西一股脑地显示出来,不过大体的形态已经有了,先发出来做个预告。

Fcitx皮肤浏览器

Fcitx皮肤查看器

有兴趣的且有Qt的人可以从github上抓下来玩玩(虽然我认为应该没多少人有兴趣……):
git@github.com:csslayer/fcitx-skin-viewer.git

用C语言重写的会员卡管理程序,欢迎Linux众测试


终于重写完毕了,不过Bug肯定很多,欢迎诸君测试。由于未在Windows下调试,且有磁盘读写的相关代码,若有热心的Windows用户想通过源代码编译还请自行删除可能产生的垃圾文件。

Linux用户可用的二进制程序:

https://skydrive.live.com/?cid=D40A6A1CE1A272AC&id=D40A6A1CE1A272AC%21507&sc=documents

源代码在此,欢迎给出指导:

http://github.com/ukyoi/cardmanage

我不太会用github,所以有些混乱,还请原谅。

最近的动作


好久没码字了。主要原因仍旧是太懒,觉得码字太耽误时间,但其实在互联网上闲逛也很耗时间,每天似乎没做什么事就流逝掉了。

当然,也不是什么事都没做。最近在把私曾经那个用Python写的代码再用C重写一遍……私承认这个想法有点蛋疼,但是这么做还是有一定原因的。首要原因是私的目标是未来使用Qt图形库,写这个文本的会员卡程序只是练练手。PyQt虽然有文档,但私找到的教程都不是很系统也不很容易理解。原生Qt是使用C++的,有详细的官方文档,所以私打算先涉及C/C++,然后把原生Qt学会,再学PyQt这样的语言绑定。所以私并非放弃Python,而是暂时先搁置一下而已。

然而直到开始写,才直到用C写程序是有多么困难。私会员卡管理程序的第一版只有180行python代码。而现在写了200+行C代码,只实现了不到一半的功能。当然也有其他的原因,第一版的程序流程控制有很大问题,误操作之后要从主菜单重新选过,非常麻烦,而现在私在写的时候会尽量考虑到误操作之后怎么处理的问题。然而总体来说,C语言的头文件、声明以及表示代码段的括号等等都是很占地方的。

而且……啃C语言的教材也是相当困难的事情……大概我在这方面没什么天赋吧……

最近的事


其实私本来早就想写一些什么了,但是由于最近的事情实在是多了些,实在很难挤出些时间码这些字。当然其实这只是私给自己犯懒找些借口而已。

也许是这几天压力确实大了些(说实话私真的想不出别的解释,因为私最近还是很关注自己的健康的),前天嗓子开始不适,昨日发展成疼痛,今天貌似疼痛有所减轻,但病灶转移,上午开始流涕,下午便已经开始发烧了。借同学的体温表量了量,37.4摄氏度,不算高。服用两片维C银翘,现在还不算很难受。

下面开始正题……

//

今日看到KDE 4.7 beta已经出了……很多激动人心的特性啊,当然不满意的地方总是有的,比如文件夹图标。总之现在正在更新中,希望打包不要有什么问题才好。

//

前一阵子看到方校长被砸中了。虽然私从情感上觉得很过瘾,但对方校长,对此事还需要冷静地看待。墙确实是越来越高,开放的互联网离我们似乎也越来越远,然而至少互联网还在中国这篇常被割草机推过的土地上艰难生长着。私不知如果墙是不可实现的,官方会不会真的做出拔线这种蠢行为。无论拔线这一行为有多蠢,按照我朝屁民们的生活哲学,这网断个十年八年绝不是什么问题。从方校长自己的角度说,假如上头有了建墙的决策而方校长却不肯合作会怎样?
当然私并不是在说方校长这样做就是正义的。然而即便贵为哈工大、北邮的校长,也不过是个凡人。凡人必须确保自己生存下去,才可能拯救别人。当然说他害了别人,这也不错。但如果他不害别人,也许会有更厉害的人来害。
而对于事件本身。短事件内墙肯定会因此继续提高(事实也确实如此),然而也许能够引起更多人的重视,从长远来看还是很有利的。

//

冷落编程很久之后,私终于又开始继续C语言的学习之路了。其实以前所谓的“没时间”、“记不住”都是些借口而已。有事件用来上推、浏览网站,自然也有事件仔细地看看书,码码字。
至于那个“理发店会员卡管理程序”,暂时的想法是用C重写,并且把“理发店”三个字去掉(因为实在没什么意义)。现在写了一些框架(例如对这小程序其实毫无必要的makefile、头文件),但在搞懂fopen、fwrite这些函数之前还有些重要内容要学习,所以暂时还不能写关键部分。大概要想实现好主要功能至少要等放假了。
为何要用C写呢?主要原因是不再需要一套Python才能运行,因此无论在Linux上还是Win上都可以一键运行了,当然也不必关心Python不同版本语法不同的问题。当然问题也是很多的,代码要重写,跨平台的时候代码要修改,Win下可能还有汉字编码问题,等等……

//

私曾撰过关于兔子的文章,那兔子其实是同楼基础医学系的同学养的。据说一直就吃百家饭,最近又跑到我们这里来了。

//

别的暂时想不起来了,就写到这里吧。

DFS解迷宫的一些想法


写在前面:私不是程序员,也不是学信科的,也只是最近开始没事的时候翻翻C语言的书。因此阁下很可能会觉得下文所述的想法很原始或是阁下早已用过了。私在此只是记录一下自己的想法。

现阶段私学C语言用的书是宋劲杉的《Linux C编程一站式学习》,里面讲深度优先搜索的时候讲的是个走迷宫问题,用1表示墙,0表示路,从左上走到右下。代码如下:

#include <stdio.h>
#define MAX_ROW 5
#define MAX_COL 5 

struct point { int row, col; } stack[512];
int top = 0;

void push(struct point p)
{
    stack[top++] = p;
}

struct point pop(void)
{
    return stack[--top];
}

int is_empty(void)
{
    return top == 0;
}

int maze[MAX_ROW][MAX_COL] = {
    0, 1, 0, 0, 0,
    0, 1, 0, 1, 0,
    0, 0, 0, 0, 0,
    0, 1, 1, 1, 0,
    0, 0, 0, 1, 0,
};

void print_maze(void)
{
    int i, j;
    for (i = 0; i < MAX_ROW; i++) {
        for (j = 0; j < MAX_COL; j++)
            printf("%d ", maze[i][j]);
        putchar('\n');
    }
    printf("*********\n");
}

struct point predecessor[MAX_ROW][MAX_COL] = {
    {{-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}},
    {{-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}},
    {{-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}},
    {{-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}},
    {{-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}},
};

void visit(int row, int col, struct point pre)
{
    struct point visit_point = { row, col };
    maze[row][col] = 2;
    predecessor[row][col] = pre;
    push(visit_point);
}

int main(void)
{
    struct point p = { 0, 0 };

    maze[p.row][p.col] = 2;
    push(p);

    while (!is_empty()) {
        p = pop();
        if (p.row == MAX_ROW - 1  /* goal */
            && p.col == MAX_COL - 1)
            break;
        if (p.col+1 < MAX_COL     /* right */
            && maze[p.row][p.col+1] == 0)
            visit(p.row, p.col+1, p);
        if (p.row+1 < MAX_ROW     /* down */
            && maze[p.row+1][p.col] == 0)
            visit(p.row+1, p.col, p);
        if (p.col-1 >= 0          /* left */
            && maze[p.row][p.col-1] == 0)
            visit(p.row, p.col-1, p);
        if (p.row-1 >= 0          /* up */
            && maze[p.row-1][p.col] == 0)
            visit(p.row-1, p.col, p);
        print_maze();
    }
    if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1) {
        printf("(%d, %d)\n", p.row, p.col);
        while (predecessor[p.row][p.col].row != -1) {
            p = predecessor[p.row][p.col];
            printf("(%d, %d)\n", p.row, p.col);
        }
    } else
        printf("No path!\n");

    return 0;
}

大概意思就是每次弹出一个栈,找到周围所有的合法步骤然后把它们栈压进栈区,下一轮时再弹出最后压入的栈,如果走入死路就再次弹栈,找另外一条可能路线,直到找到迷宫的解。如果栈空了就说明无路可走,打出“No path !”。
但是找到解之后就比较麻烦。如何输出路径呢?这段代码使用的方法是建立一个predecessor数组,用来表示每个点的前趋,然后从最后一个点顺藤摸瓜往前找,逐渐打印出来。但这个predecessor数组占用的空间很大,且只能从后向前找,不便于找到其中的某个特定步骤。怎么解决这一问题呢?作者宋劲杉出了一道思考题,还问读者能够想出几种方法,可见方法是不止一种的。

在正确理解这段代码之前,私一直认为栈区中至少应该存有正确路径走过的所有的点,后来才明白这个想法是有问题的。由于每个循环开始时栈顶已经弹出了,此时如果再在这一点继续向深处找,新压入的栈就会把刚才弹出的栈顶覆盖掉,这样找到最后整个栈中在极端情况下(例如整个迷宫都没有岔路)可能一个点也不剩。此外一个循环内是4个方向均会搜索,所以如果有岔路,就会压入多个点,但弹出时只会弹出一个点,所以栈中会残留错误道路的点。

怎么修改呢?其实很容易。原来每个循环会从栈顶弹出一个栈,其实找到路径之后,完全可以把这一栈再压回去,比如这样:

int main(void)
{
    struct point p = { 0, 0 };

    maze[p.row][p.col] = 2;
    push(p);

    while (!is_empty()) {
        p = pop();
        if (p.row == MAX_ROW - 1  /* goal */
            && p.col == MAX_COL - 1)
            break;
        if (p.col+1 < MAX_COL     /* right */
            && maze[p.row][p.col+1] == 0) {
            top++;
            visit(p.row, p.col+1, p);
        } else if (p.row+1 < MAX_ROW     /* down */
            && maze[p.row+1][p.col] == 0) {
            top++;
            visit(p.row+1, p.col, p);
        } else if (p.col-1 >= 0          /* left */
            && maze[p.row][p.col-1] == 0) {
            top++;
            visit(p.row, p.col-1, p);
        } else if (p.row-1 >= 0          /* up */
            && maze[p.row-1][p.col] == 0) {
            top++;
            visit(p.row-1, p.col, p);
        }
        print_maze();
    }

    int i;
    if (!is_empty()) {
        for (i=0; i<top; i++)
            printf("(%d, %d)\n", stack[i]);
    } else
        printf("No path.\n");

    return 0;
}

如果找到了可用的路,由于有了top++,相当于仅仅是读取到栈顶的数据(而栈没有弹出),这样在搜索路径压栈的时候就不会把原来的栈顶覆盖掉,从而保证了栈中存在所有正确路径上的点。解决残留错误道路的点的问题也很容易,只需让一个循环内如果搜索到了一条可以深入的路径就停止(用else实现),否则再搜索下一条路径。如果找到死路栈会逐个弹出,直到岔路口,所以不用担心找不到正解。到最后如果迷宫存在解的话,栈中所保存的就是正确的路径。由于例子中的“栈”是一个数组(当然您可能会反驳说这个例子中stack[]并不是真正意义上的栈,“栈”在理论上应该只能访问顶端的元素,但那只是个形式问题),于是可以很容易地访问任何一步的点,输出的时候正着打反着打跳着打均可,程序中所有和predecessor有关的内容便可以删去了。

但是事实上这个办法在程序效率上是劣化而不是优化,首先虽然不需要predecessor了,但是栈空间消耗变得更大。原本栈空间的消耗和分岔路的多少有关,现在则是和路径的长度有关而与有多少岔路无关。那么栈空间的最小值应该是多大呢?私还没考虑好,但分配为整个迷宫的大小肯定是足够的。还有个问题是原来的方式由于覆盖了之前走过的路径,只保留岔路上其他方向的点,如果找到死路,弹栈后会直接跳到另外一个可能的岔路继续搜索,而修改后的方法如果找到死路,必须沿原路一步步跳回岔路口。再加上top++所用的时间,程序效率比原来要低,而且岔路越多越深入,效率上的差距越明显。

前几天看到个好玩的推,说“情人节我玩一天连连看,消灭一对是一对”。私寻思既然学了DFS和BFS,不如自己也写个文本连连看玩玩?再说吧。

另:为嘛我Tag里写C,wordpress会给自动补全成C++?