Monday, 9 March 2015

Vim tips

The vim text editor has many features, and I have often felt that my use of it is suboptimal. Here are some features I found out about that are useful which I wish I knew about earlier.

The ":scriptnames" command. This shows a list of the scripts that vim read when starting up. Useful for debugging vim configurations.

To show where a particular vim variable got its value, use ":verb set". This is useful when a vim setting fails to have to value you thought you gave it, because it has been overridden somewhere else.

Sometimes when wanting to open a new line above the one I was on, I would type ESC O, and this would break in a confusing way when I tried typing the contents of the new line. I didn't even know how I got into this state when it happened. Eventually, I realised this could be fixed by using "set ttm=20" in my "vimrc" file. (the ttm setting is also known as ttimeoutlen).

The "a" flag of "formatoptions" is very useful for automatically formatting text as you type it. Turn off for filetypes where newlines are important, for example with "autocmd FileType sh setl fo-=a" for shell scripts.

The "\zs" and "\ze" sequences in regexps are very useful for inserting text before or after certain places. For example, ":%s/\zebanana/the /g" inserts the text "the " before any occurrence of the text "banana". Without this feature you would have to type "%s/\(banana\)/the \1/g", which is harder.

"gv" to reselect the last visual selection - for example, to perform a search and replace on it, or a shift.

Remapping the Caps Lock key to Esc is very helpful - before this I was starting to get a sore little finger by constantly pressing "Control-C".

I have the following to turn off "comment leader" insertion, (like "*" for "/* ... */" comments in C), which I found very difficult to turn off and keep off.
    set fo-=c
    set fo-=r
    set fo-=o

    " Disable "comment leader" garbage
    set com=

    I have the following to disable the gaudy bright yellow search matching:

    set hlsearch
    set hl=ls

    I find the following useful for reformatting lines:

    map <CR> i<CR><Esc>

    This is useful for editing numbers leading zeroes in dates:
    set nrformats-=octal


    Wednesday, 22 October 2014

    Port of Last Eichhof game to Allegro library

    There was a game called "Last Eichhof" released in 1993 for MS-DOS. The source code was released by the original coder (Danny Schoch) - I don't know exactly when - and is available from various places on the Internet.

    Earlier this year I ported this code to the Allegro games programming library (http://alleg.sourceforge.net/). The source is available from here. The ported version smooths the motion of sprites on the screen and makes the controls more responsive (so the minimum amount you can move your ship with a tap of a key is smaller).

    There were several points of interest. The assembly routines had to be rewritten completely, of course, which included the main game loop and the graphics routines. I had to learn about the parts of the VGA programming interface that the original used - but I feel I only scratched the surface of it, as it is quite horribly complicated. I managed to avoid having to read much about Sound Blaster programming - the only complication was how to decode the sound samples in the data files, which were in something called 8-to-4 bit ADPCM. Fortunately I found an algorithm on a website for decoding it.

    Another unusual feature of the source was the format of its data files, and how they were read. There are lines like this in the original source code (from GAMEPLAY.C):

    // Load weapon sprite library.
       ptr = loadfile(datapool, "weapons.sli");
       weapon.nsprites = *ptr;
       weapon.sprite = (struct sTableEntry *)(ptr+1);
       for (i = 0; i < weapon.nsprites; i++) {
          (long)weapon.sprite[i].sprite += (long)weapon.sprite;
       }

    The call to 'loadfile' gives a pointer to the data. This starts off as a number of sprites, followed by offsets into the data, followed by the data itself. 'weapon.sprite' is set to point directly into this data area. This is an array of 'struct sTableEntry', with the exception that the 'sprite' fields of each 'struct sTableEntry' are a kind of offset, and not pointers. The loop converts these offsets into true pointers (of type 'struct sprstrc * far'), each a pointer to a structure with information about the sprite in it.

    There are a few tricky points here. The first is that the compiler has to give 'struct sTableEntry' exactly the right layout for this to work, with the right padding, widths and endianness. This kind of "deserialization" is not portable between compilers or architectures.

    Converting the "long" fields into far pointers relies on a "long" type being the same width as a far pointer. (Note C programming for MS-DOS had two kinds of pointer, called "near" and "far". The "far" pointer was 32 bits and could access a wider address space than the 16-bit near pointer.) Note that the "offsets" are not simple numbers giving the number of bytes into the data area: they are a very strange type of value, being the numeric difference between two far pointers considered as numbers. This would appear very dodgy - whether we get a pointer to the right place could depend on the address of 'weapon.sprite'.

    These lines took me some time to understand what is going on, considering that a cast on the left-hand side of an assignment statement is not supported by modern C compilers. I believe it means:

    weapon.sprite[i].sprite = (struct sprstrc * far) ((long) weapon.sprite + (long) weapon.sprite[i].sprite);
    When I was playing this game under MS-DOS, it took me a long time to beat, and I only beat it once. I have beaten the original once or twice in DosBox, after practising on the Allegro version. I think the Allegro version is slightly easier because of the motion smoothing making it easier to see where enemies are moving on the screen, as well as the more responsive controls.

    Thursday, 31 July 2014

    Notes on compatability

    There are two types of compatibility in software: new versions of programs reading old data (backward compatibility), and old versions of program reading new data (forward compatibility).

    Backward compatibility is the easiest to achieve, as it does not require predicting the future. How can we ensure forward compatibility then? By imagining reasonable ways to extend the data format and making sure that the program works with these - e.g., doesn't break on unknown options. One suggestion has been to use a version number in your data format. However, I read that this failed for MIME, because no-one knew what the correct behaviour was if they got a different version number.

    A related problem is interoperability. This refers to reading data produced by a program whose creation or maintenance is buereaucratically or socially separated from your control, and producing data to be read by this program.

    How can we ensure interoperability? One way is that if you are inventing a file format or network protocol, make sure you define it thoroughly. Otherwise, other people will implement it all in slightly different, incompatible ways, and it will be impossible to be compatible with them all. (I heard that CSV was an example of this.) A similar way is make your data format as easy to parse as possible, otherwise other people may do it wrong. (See this page about different termcap versions for an example of a format with varying implementations.)

    One way to encourage interoperability and backward compatability is intolerance of malformed syntax. This is a herbicide on the proliferation of degenerate data and means that new implementations do not have the burden of supporting degenerate data that people are relying on. How much you can get away with this depends on the share of usage of your program.


    Saturday, 22 March 2014

    Principles of GUI's

    I had a few thoughts a while back about GUI's like Microsoft Windows and what principles they should follow.

    * Ease of switching between applications
    * Each user "command" should only have one interpretation and be read by only one program. For example, if you are scrolling a page by clicking the middle mouse button, another application shouldn't use that mouse click to do something else.
    * Lack of excess flexibility and useless information. For example, you do not need to be able to position desktop or folder icons at arbitrary pixel locations. This could apply to the window paradigm itself - moving non-maximized windows around is not that fun. Another example is the dotted outline around the active widget - could be an icon, or a command button. Only used for keyboard input and worries the user whether they will accidentally activate a command.
    * Uninterruptability - I.e. one program should not be able to become more prominent, taking up the screen and redirecting input commands to itself (maybe interrupting a sequence of commands intended to be received by another program/interface). An example is "splash screens" for programs. Another is the Windows Start Menu - several times when Windows has been starting up the Start Menu has disappeared while I'm trying to use it.
    * Non-persistence of "high-energy" states. For example, desktop icons selection. Desktop icons should only be highlighted when the user is immediately about to do something with them. Otherwise there is a constant worry that they will accidentally rename or delete something with a single mouse click or key press.
    * Lack of pointless distractions ("Unused icons in your system tray have been hidden.").
    * Appearance of expensiveness of operations - for example, opening a elementary GUI feature like a menu or clicking on a tab in a tabbed interface shouldn't perform expensive calculations, load other programs or load data from slow external storage or network connections.

    Updated 3rd June 2014.

    Keyboard auto-repeat

    I figure that keyboard auto-repeat isn't always desirable.

    Take a web browser for example. You press and hold the down button, and the page scrolls down, and then stops, and then starts again and continues scrolling until you lift the button.

    It should be more like a video game: start scrolling down, smoothly, as soon as the key is depressed, and cease scrolling as soon as it is lifted.

    In X11, the server sends events to programs - KeyPress and KeyRelease. If autorepeat is on many of these events are generated for one (physical) keypress. You can turn it off with "xset -r". I'd like to try only turning it on for the arrow keys, but there isn't an easy way to do this - you have to turn on autorepeat, and then turn it off for all keys except the ones you want to leave it on for.

    Friday, 1 November 2013

    Notes on Slackware 14.0 upgrade

    I upgraded from version 13.37 to 14.0 of Slackware, bringing me up-to-date. This time I used the slackpkg program, and it went quite smoothly. I followed the instructions at http://docs.slackware.com/howtos:slackware_admin:systemupgrade.

    In short,

    • Edit /etc/slackpkg/mirrors to add the address of a mirror of 14.0
    • Blacklist kernel packages in /etc/slackpkg/blacklist
    • Run "slackpkg update" to download new package list
    • Then it was running "slackpkg upgrade slackpkg", "slackpkg upgrade glibc-solibs", "slackpkg install-new", and "slackpkg upgrade-all".
    • "slackpkg clean-system" to delete old packages, making sure not to delete any packages I've installed separately from the Slackware distribution. This is done in conjunction with reading "CHANGES_AND_HINTS.TXT".
    Then I needed to check new configuration files under /etc. "slackpkg new-config" can be used to move these files across. I needed to keep the Internet connection settings in /etc/rc.d/{rc.inet1.conf,rc.wireless.conf} and /etc/wpa_supplicant.conf. Also /etc/inittab for the behaviour of Ctrl+Alt+Del.

    I downloaded and installed kernel packages manually and rerun lilo after adding lines to /etc/lilo.conf:

    image = /boot/vmlinuz-huge-smp-3.2.29-smp
      root = /dev/sda3
      label = Linux_slack14
      read-only
      append = "acpi_osi=Linux"

    The last line is needed on my craptop for the screen dimming keys to work.

    I previously used ndiswrapper to get my wireless card to work, but I thought I would try the Linux drivers instead. I had several modules blacklisted in "/etc/modprobe.d/blacklist.conf", and running "modprobe b43" made the wlan0 interface show up. I've used wireless since then with few problems. [1]

    Next steps are to carefully remove files in /etc/modprobe.d according to CHANGES_AND_HINTS.TXT, use the generic kernel with an initrd instead of the huge kernel, and look through old configuration files (saved as "*.orig") to see what has changed and if I want to keep anything else.

    Biggest change has been the upgrade to fvwm 2.6.5. When I tried this before I would get random crashes, but I haven't had any so far.

    [1] I don't know why I couldn't get this working before - I suspect it would have worked but I was doing something else wrong, e.g. with ifconfig, wpa_supplicant or dhcpcd. I spent so long trying to get wireless to work that I stopped playing with it as soon as I got it to work with ndiswrapper.

    Wednesday, 14 August 2013

    Notes on upgrade from Slackware 13.1 to 13.37

    I recently upgraded from Slackware 13.1 to 13.37. 13.37 is not the latest version (14.0 is), but by only upgrading one release at a time I'm less likely to have problems.

    I downloaded the files with


    wget --no-parent -nH --cut-dirs=3 -r -nc (path)

    Options mean
    --no-parent - Don't download linked files above the directory in the given path
    -nH - Don't create local directory called ftp.slackware.com etc.
    --cut-dirs - Don't create local directories based on first 3 directories in path.
    -nc - Don't download existing files - useful if wget is interrupted.

    I had to download and compile the ndiswrapper Slackbuild. I had problems compiling it - I think I ought to have compiled it before installing all the new Slackware packages.

    I found the screen went blank when I rebooted! It turned out that the brightness on my laptop screen was only turned down. (See http://www.linuxquestions.org/questions/slackware-14/black-screen-on-bootup-after-a-few-lines-slack-13-37-a-882680/page2.html.) I haven't fixed this yet; all I do is use the laptop function keys to turn the brightness up.)

    The instructions for creating an initrd were unclear: I found I had to run

    mkinitrd -c -k 2.6.37.6-smp -m ext3

    (I was missing the "-smp" part.)

    I had some problems with X11. My mouse pointer wouldn't move. I had to delete /etc/X11/xorg.conf. The keyboard was in US mode now, and I had to copy a keyboard layout file (90-keyboard-layout.conf) to /etc/X11/xorg.conf.d.

    I found "xset m" would no longer set the pointer speed for my Synaptics touchpad. I had to use a program called "synclient" instead. I copied a settings file to "/etc/X11/xorg.conf.d/50-synaptics.conf". There was some subtle and annoying behaviour such as taps taking too long to be recognized and sometimes getting middle clicks, which I fixed with adding the following lines to 50-synaptics.conf:

            Option "FastTaps" "on"
            Option "MinSpeed" "0.5"
            Option "MaxSpeed" "10"
            Option "AccelFactor" "0.05"
            Option "EmulateTwoFingerMinZ" "1000"
            Option "EmulateTwoFingerMinW" "1000"

    I had some broken behaviour with fvwm. Apparently Unicode locales are broken in versions (2.4.*) (even though it worked before.) It could have been something to do with the fonts changing as well.

    The new version of Thunderbird would no longer highlight folders with new messages. I managed to get something acceptable by changing "~/.thunderbird/h0eadjtl.default/chrome/userChrome.css" to:

    /*
     * Do not remove the @namespace line -- it's required for correct functioning
     */
    @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */

    treechildren::-moz-tree-cell-text(folderNameCol, newMessages-true) {
      color: red !important; }