Making Windows Usable for Old Linux Farts

I've been using Linux almost exclusively on my laptops since 1999. I've now (September 2004) bought a new laptop and decided to give Windows another try. On this page I'll assemble some information I've gathered which helped me to make Windows more usable for me. The main reason for its existence is that I can refer to it should I ever have to reinstall the OS. But if someone else thinks the stuff here is useful, he/she is invited to use it... :)

 

Contents


 

Preliminaries

Note that I'm using an English version of Windows XP professional SP2. If you're using another Windows version you're probaly out of luck. I'm also using a German keyboard which might or might not be relevant for some of the topics below.

And of course, as is you didn't know that already, if you apply any of this information to your own system, you may end up causing damage to your software, your hardware, or your mental health. I take no responsibility for anything that you may do as a result of reading this page. The contents of this page are provided 'as is' with no warranty. Yada, yada, yada...

If you want to know when or if this page has been updated check the CVS header at the bottom.
 

Software Recommendations

Notes about some programs that I've installed and find very useful for my daily work:
 

Cygwin/X

Cygwin/X is a free X server for Windows which can be installed via Cygwin. I've used X-Win 32 before and had several issued with it. Luckily, all these problems are gone with recent versions of Cygwin/X so I've dumped X-Win 32 completely.

I have a batch file startwin.bat in my Windows Startup folder that looks like this:

@echo off
SET DISPLAY=127.0.0.1:0.0

SET CYGWIN_ROOT=\cygwin

SET PATH=.;%CYGWIN_ROOT%\bin;%CYGWIN_ROOT%\usr\X11R6\bin;%PATH%

SET XAPPLRESDIR=/usr/X11R6/lib/X11/app-defaults
SET XCMSDB=/usr/X11R6/lib/X11/Xcms.txt
SET XKEYSYMDB=/usr/X11R6/lib/X11/XKeysymDB
SET XNLSPATH=/usr/X11R6/lib/X11/locale

if not exist %CYGWIN_ROOT%\tmp\.X11-unix\X0 goto CLEANUP-FINISH
attrib -s %CYGWIN_ROOT%\tmp\.X11-unix\X0
del %CYGWIN_ROOT%\tmp\.X11-unix\X0

:CLEANUP-FINISH
if exist %CYGWIN_ROOT%\tmp\.X11-unix rmdir %CYGWIN_ROOT%\tmp\.X11-unix

run XWin -nounixkill -multiwindow -clipboard -silent-dup-error -xkblayout de -xkbrules xfree86 -xkbmodel pc105 -xkbvariant nodeadkeys
This also takes care of my German keyboard.

I had one problem with Cygwin/X: If I connect to another machine (or even to localhost) via Cygwin's ssh and with X11 forwarding then remote Emacsen will mysteriously crash under certain circumstances. Turns out that this doesn't occur with SecureCRT. So I now have a tiny VB script Emacs.vbs like this one

# $language = "VBScript"
# $interface = "1.0"

Sub Main
  crt.Screen.Send "emacs && exit" & VbCr
  crt.window.Show(0)
End Sub
and use it as a "logon script" for SecureCRT. (Note that the two comment lines are necessary!) This enables me to have entries in True Launch Bar which directly open remote Emacsen with one mouse click or a keyboard shortcut.
 

VMWare

VMWare is indispensable for my development work. I have several Linux virtual machines on my hard drive and use them to develop web applications. It's cool that you can effortlessly copy a working installation (even between different machines) and that you can make snapshots before you're trying something "dangerous." You can also share folders between virtual machines and Windows - nice.

Unfortunately they don't support Debian as a guest OS so you have to invest some time if you want to use the VMWare Tools (and you should use them): I installed a minimal Debian testing system from a Sarge business card ISO from cdimage.debian.org and used a 2.4 kernel - from my experiences VMWare doesn't seem to be ready for 2.6 yet. Then I grabbed a matching kernel source, copied the .config file from the /boot directory and built the kernel with make-kpkg. This will give you the same kernel you already have installed but now you have the kernel headers the VMWare Tools installer wants to know about. Once you have VMWare Tools installed you'll have DMA access to your hard drive and a clock which is synchronized with your host OS.

I usually minimize VMWare into the tray via ToggleMinimize and then use it through SecureCRT. I generally don't use the X Server of the virtual machine.

Note that VMWare will not cleanly survive if you hibernate your system or put it into standby mode. I hope they'll fix this in the future.
 

Firefox

I use Firefox as my default browser. If an external application (like, say, Emacs with browse-url) opens a link I don't want it to use a new window but a new tab instead. It turns out that this can be easily accomplished with Tabbrowser Extensions.

In my user.js I have these lines:

user_pref("browser.block.target_new_window", true);
user_pref("nglayout.initialpaint.delay", 0);

 

The Registry

Here are some general registry tweaks that I found useful: Note: The registry entries above and in the following sections are (unless I've made a mistake) designed in such a way that you can paste them directly into a .REG file (which should start with a line like "REGEDIT4").
 

Emacs

And now for the most important program - Emacs. I use the native Windows version of GNU Emacs which is also known as NT Emacs. I usually grab binary CVS builds from the ntemacs project at Sourceforge.

To start Emacs maximized put this line at the end of your ~/.emacs file:

(w32-send-sys-command 61488)
If you don't want the Emacs tool bar you can add the line (tool-bar-mode 0) to your ~/.emacs file but Emacs won't fully maximize in this case - the real estate occupied by the tool bar is lost. You have to disable the tool bar in the registry to get it back:
[HKEY_LOCAL_MACHINE\SOFTWARE\GNU\Emacs\Emacs.Toolbar]
@="0"

Some key bindings I use:

(defun my-delete-frame ()
  "Deletes the current frame. If this is the last frame, quit Emacs."
  (interactive)
  (if (cdr (frame-list))
    (delete-frame)
    (save-buffers-kill-emacs)))
(global-set-key [\M-f4] 'my-delete-frame)

(defun my-make-frame ()
  "Make a new frame and maximize it."
  (interactive)
  (w32-send-sys-command 61488 (make-frame)))
(global-set-key "\M-n" 'my-make-frame)

Gnuserv

Gnuserv is a very useful addition for Emacs, especially on Windows. Its main task is to make sure that you always have only one Emacs running and that all newly opened files are opened by this one Emacs. This is achieved by a client/server architecture with the help of two applications external to Emacs.

Unfortunately, Gnuserv is not very well documented and there doesn't seem to be an active maintainer. I got my copy from http://www.wyrdrune.com/Files/gnuserv.zip.

Update: In the meantime, Guy Gascoigne-Piggford has released a new (beta) version of Gnuserv which is available from http://www.wyrdrune.com/Files/.

To install Gnuserv unzip the archive and copy gnuserv.exe and gnuclientw.exe to your Emacs bin directory. Copy gnuserv.el to a folder which is in your load path and optionally byte-compile it. Finally, add the lines

(require 'gnuserv)
(setq gnuserv-frame (car (frame-list)))
(gnuserv-start)
to your ~/.emacs file. The second line will prevent Emacs from creating a new frame each time it is invoked via gnuclientw - leave it out if you like this behaviour.

There's one little problem: If Emacs was minimized and is afterwards invoked via gnuclientw it won't return to its maximized state. This is actually a shortcoming of NT Emacs which doesn't behave like a good Windows citizen and not a problem of gnuclientw. However, it is easy to get rid of this. Patch gnuclient.c like so

--- gnuclient.c~        1999-09-11 22:37:24.000000000 +0200
+++ gnuclient.c         2004-10-11 20:31:22.433531000 +0200
@@ -85,16 +85,19 @@
        return (TRUE);
 }
 
-void showEmacs( int unIconify )
+void showEmacs( int unIconify, int maximize )
 {
   HWND hWnd = FindWindow ("Emacs", NULL);
   if ( hWnd ) {
     /* Is the Emacs window iconified ? */
     if (IsIconic (hWnd)) {
       if (unIconify) {
-        ShowWindow (hWnd, SW_SHOWNORMAL);
+        ShowWindow (hWnd, SW_SHOW);
         /* Need this since Emacs thinks it is still iconified otherwise! */
         SendMessage (hWnd, WM_SYSCOMMAND, SC_RESTORE, 0);
+        if (maximize) {
+          SendMessage (hWnd, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
+        }
       }
     }
     else {
@@ -181,6 +184,7 @@
 #if defined( WIN32 )
   int wintofront = 0;                          /* wintofront flag */
   int uniconify = 0;                           /* uniconify flag */
+  int maximize = 0;                            /* maximize flag */
   int runEmacs = 0;
 #endif
 #ifdef INTERNET_DOMAIN_SOCKETS
@@ -208,7 +212,7 @@
 
 #ifdef INTERNET_DOMAIN_SOCKETS
 #if defined( WIN32 )
-                    "h:p:r:qfFxe"
+                    "h:p:r:qfFMxe"
 #else
                     "h:p:r:qe"
 #endif
@@ -243,6 +247,11 @@
     case 'F':
       wintofront = 1;
       uniconify = 1;
+      break;    
+    case 'M':
+      wintofront = 1;
+      uniconify = 1;
+      maximize = 1;
       break;
     case 'x':                          /* Just run and or show emacs  */
       runEmacs = 1;
@@ -276,7 +285,7 @@
 
 #if defined( WIN32 )
   if ( runEmacs ) {                   /* just run and or show Emacs and then exit */
-    showEmacs( 1 );
+    showEmacs( 1, maximize );
     exit(0);
   }
 #endif
@@ -373,7 +382,7 @@
 
 #if defined( WIN32 )
   if (wintofront)
-         showEmacs( uniconify );
+         showEmacs( uniconify, maximize );
 #endif
 
 #ifdef SYSV_IPC
and re-build gnuclientw.exe. If you don't want to do this you can download the whole archive including the patch and binary executables from http://weitz.de/files/gnuserv.zip. The patch adds a new command-line option "-M" which, if present, makes sure Emacs is maximized after it has been uniconified. It also automatically adds the options "-f" (bring Emacs window to front) and "-F" (uniconify if necessary). Note that in all of the following recipes you can replace "-M" with "-F" or "-f" or leave it out completely. If you do this you don't have to use the patched version of gnuclientw.

Now that you have Gnuserv installed you can create a Windows shortcut for Emacs with the following target:

c:\emacs\bin\gnuclientw.exe -M "%1"
You can drag any file on this shortcut and it will open in Emacs. If Emacs is already running the file will just be opened in a new buffer, otherwise Emacs will be started. You can also double-click this shortcut to start Emacs or bring a running Emacs to the front. However, Emacs will try to open a file named "%1" in this case (which most likely doesn't exist). You can prevent this from happening by adding the following form to your ~/.emacs file:
(defadvice server-find-file (around ignore-percent-one (file) activate)
  (if (or (not (string= "%1" (file-name-nondirectory file)))
          (file-exists-p file))
    ad-do-it))
I've added the shortcut described above to True Launch Bar and assigned it a hot key.

But it gets even better: You can add an entry to the Explorer context menu such that if you right-click on any file you'll get an option to edit this file with Emacs:

[HKEY_CLASSES_ROOT\*\shell\Emacs]
@="Edit with &Emacs"
[HKEY_CLASSES_ROOT\*\shell\Emacs\command]
@="c:\\emacs\\bin\\gnuclientw.exe -M \"%1\""
Cool, isn't it?

If you want to open specific file types directly in Emacs by double-clicking them you can do that, too. Here's an example for text (.TXT) files:

[HKEY_CLASSES_ROOT\txtfile\shell\open\command]
@="c:\\emacs\\bin\\gnuclientw.exe -M \"%1\""
If you plan to do this with a lot of file types then maybe you can save some time with this Emacs Lisp code:
;; w32-reg-int is available from <http://www.gnusoftware.com/Emacs/Registry/>
;; you must also install registry.exe, available from the same location
(require 'w32-reg-int)

(require 'cl)

(defvar reg-open-suffixes
    '("txt" "el" "php" "xml" "xsl" "system" "asd" "lisp" "cl")
  "If you want a file type to be opened by Emacs if you double-click
its icon, add its suffix here.")

(defvar reg-edit-suffixes
    '("asm" "cc" "cs" "cpp" "pl" "c" "htm" "csv" "h" "bat" "java" "js" "py" "pm" "sql")
  "If you want a file type to be edited by Emacs if you right-click
its icon and choose \"Edit\", add its suffix here.")

(defvar gnuclientw "c:\\emacs\\bin\\gnuclientw.exe"
  "Where gnuclientw is located")

(defun convert-gnuclientw ()
  (let (pos
        (gnuclientw-temp gnuclientw))
    (while (setq pos (string-match "\\\\" gnuclientw-temp pos))
      (setq gnuclientw-temp (replace-match "\\\\" t t gnuclientw-temp))
      (incf pos)
      (incf pos))
    gnuclientw-temp))

(defun create-registry-entries* (suffix-list &optional edit)
  (dolist (suffix suffix-list)
    (lexical-let ((class (cdr (w32-reg-interface-read-value (format "HKEY_CLASSES_ROOT\\.%s\\" suffix)))))
      (when class
        (insert (format "[HKEY_CLASSES_ROOT\\%s\\shell\\%s\\command]
@=\"%s\"
"
                        class (if edit "edit" "open")
                        gnuclientw-for-regedit))))))

(defun create-registry-entries ()
  (interactive)
  (let ((gnuclientw-for-regedit (format "%s -M \\\"%%1\\\""
                                        (convert-gnuclientw))))
    (insert "REGEDIT4

")
    (create-registry-entries* reg-open-suffixes)
    (create-registry-entries* reg-edit-suffixes t)))
Change the variables to your liking, load the code with load-file, open a new buffer and then M-x create-registry-entries. Now save this buffer as a .REG file.

You can do more cool stuff with Gnuserv: With the following registry entry you can right-click any folder and open it with Dired:

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\Dired]
@="Emacs &Dired"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\Dired\command]
@="c:\emacs\bin\gnuclientw.exe -M \"%L\""
And with this one you can click on "mailto" links in your web browser and compose the email in Gnus:
[HKEY_CLASSES_ROOT\mailto\shell\open\command]
@="c:\\emacs\\bin\\gnuclientw.exe -M -e \"(gnus-msg-mail (substring \\\"%1\\\" 7))\""

Sending Email

The rest of this text is probably only interesting to you if you're a laptop user and want to have secure (encrypted) connections to your email servers no matter where you are.

I want to send my email with Emacs and Gnus but I couldn't get starttls.exe to play nicely with NT Emacs although it compiles cleanly on Cygwin. Using a local SMTP server on my laptop is not an option either because many email servers nowadays reject email coming from dynamic IP addresses. Therefore I use the following scheme: I try to guess from certain heuristics whether I'm online and where I am. I use this result to either select a specific SMTP server within the same LAN or to connect to a shell account via ssh and use its local sendmail daemon or to queue the mail if I'm offline. To achieve this I have these lines in my ~/.emacs file:

(setq user-full-name "Edi Weitz")
(setq user-mail-address "edi@agharta.de")

(defadvice feedmail-buffer-to-binmail (around change-shell () activate)
  "Make sure feedmail uses Cygwin's bash shell."
  (let ((shell-file-name "sh"))
    ad-do-it))

(defvar my-net-location nil
  "Holds a symbol which describes my current location based on the
network settings. NIL means that I'm offline.")

(defun get-my-net-location ()
  "Update the value of `my-net-location' based on certain heuristics."
  (interactive)
  (setq my-net-location
          (let ((ipconfig (shell-command-to-string "ipconfig")))
            (cond ((and (string-match "Default Gateway.*: 10\\.0\\.1\\.1" ipconfig)
                        (zerop (call-process "ping" nil nil nil "-n" "1" "miles")))
                   'home)
                  ((and (string-match "Default Gateway.*: 192\\.168\\.0\\.1" ipconfig)
                        (zerop (call-process "ping" nil nil nil "-n" "1" "luna")))
                   'work)
                  ((string-match "Default Gateway" ipconfig)
                   'other)
                  (t nil)))))

(defun my-send-mail ()
  "Send email depending on the value of `my-net-location'."
  (case my-net-location
    (home
     ;; use SMTP server at home
     (let ((smtpmail-smtp-service 25)
           (smtpmail-smtp-server "my.mailserver.de"))
       (smtpmail-send-it)))
    (work
     ;; use customer's SMTP server
     (let ((smtpmail-smtp-service 25)
           (smtpmail-smtp-server "customer.mail.com"))
       (smtpmail-send-it)))
    (other
     ;; login via SSH to one of my servers and then send via local
     ;; sendmail
     (let ((feedmail-enable-queue nil)
           (feedmail-binmail-template "ssh shell.account.de -e none /usr/sbin/sendmail %s"))
       (feedmail-send-it)))
    (otherwise
     ;; offline - queue it
     (let ((feedmail-enable-queue t))
       (feedmail-send-it)))))

(setq message-send-mail-function 'my-send-mail
      send-mail-function 'my-send-mail)

(setq feedmail-enable-queue t)
(setq auto-mode-alist
        (cons '("\\.fqm$" . mail-mode) auto-mode-alist))

(defun maybe-feedmail-queue-reminder ()
  "Run feedmail queue if online."
  (interactive)
  (when (get-my-net-location)
    (feedmail-run-the-queue-global-prompt)))

(add-hook 'gnus-after-exiting-gnus-hook 'maybe-feedmail-queue-reminder)

(get-my-net-location)
Note that you need to patch feedmail.el to make this work because it has /bin/sh hardcoded as the shell to use for feedmail-binmail-template:
--- feedmail.el~        2004-10-08 22:41:10.022078400 +0200
+++ feedmail.el         2004-10-08 22:43:26.958984000 +0200
@@ -1302,17 +1302,18 @@
 
 
 (defcustom feedmail-binmail-template (if mail-interactive "/bin/mail %s" "/bin/rmail %s")
-  "*Command template for the subprocess which will get rid of the mail.
-It can result in any command understandable by /bin/sh.  Might not
-work at all in non-Unix environments.  The single '%s', if present,
-gets replaced by the space-separated, simplified list of addressees.
-Used in feedmail-buffer-to-binmail to form the shell command which
-will receive the contents of the prepped buffer as stdin.  If you'd
-like your errors to come back as mail instead of immediately in a
-buffer, try /bin/rmail instead of /bin/mail (this can be accomplished
-by keeping the default nil setting of `mail-interactive').  You might
-also like to consult local mail experts for any other interesting
-command line possibilities."
+  "*Command template for the subprocess which will get rid of the
+mail.  It can result in any command understandable by the shell named
+by `shell-file-name'.  Might not work at all in non-Unix
+environments. \(Note that on Cygwin you can use the value \"sh\".) The
+single '%s', if present, gets replaced by the space-separated,
+simplified list of addressees.  Used in feedmail-buffer-to-binmail to
+form the shell command which will receive the contents of the prepped
+buffer as stdin.  If you'd like your errors to come back as mail
+instead of immediately in a buffer, try /bin/rmail instead of
+/bin/mail (this can be accomplished by keeping the default nil setting
+of `mail-interactive').  You might also like to consult local mail
+experts for any other interesting command line possibilities."
   :group 'feedmail-misc
   :type 'string
   )
@@ -1328,7 +1329,8 @@
   (set-buffer prepped)
   (apply
    'call-process-region
-   (append (list (point-min) (point-max) "/bin/sh" nil errors-to nil "-c"
+   (append (list (point-min) (point-max) shell-file-name
+                 nil errors-to nil shell-command-switch
                 (format feedmail-binmail-template
                         (mapconcat 'identity addr-listoid " "))))))

Reading Email

Connecting to an SSH-enabled IMAP server via Gnus once used to work for me but it doesn't anymore. From various Usenet postings I see that I'm not the only one. There seems to be a problem between openssl and NT Emacs. I now use SSH port forwarding instead. This requires that you also have a shell account on the mail server to which you can connect via ssh without entering your password. My IMAP server is configured to listen for SSL connections on 993. It also listens for unencrypted connections on 143, but only on 127.0.0.1. This is in my ~/.gnus:
(setq gnus-secondary-select-methods '((nnimap "mail-imap"
                                      (nnimap-address "localhost")     
                                      (nnimap-server-port 7777))))
And this is in my ~/.emacs:
(defadvice imap-starttls-p (around always-nil (buffer) activate)
  ;; we don't want to use starttls even if it's offered by the server
  nil)

(defvar imap-tunnel-process nil
  "The process which creates the ssh connection for tunneling.")

(defvar imap-tunnel-buffer (generate-new-buffer " *imap-tunnel*")
  "The buffer for imap-tunnel-process.")

(defvar real-imap-server "my.imap.server.de"
  "The real address of the imap server. Set nnimap-server to
\"localhost\".")

(defvar real-imap-server-port 143
  "The real port of the imap server. Set nnimap-server-port to the
local port you want to use for forwarding.")

(defun maybe-destroy-imap-tunnel ()
  (when (processp imap-tunnel-process)
    (message "Destroying imap tunnel")
    (unwind-protect
        (delete-process imap-tunnel-process)
      (setq imap-tunnel-process nil))))

(defun maybe-create-imap-tunnel ()
  (cond ((and (processp imap-tunnel-process)
              (memq (process-status imap-tunnel-process) '(open run))))
        (t
         (maybe-destroy-imap-tunnel)
         (message "Creating imap tunnel through %s:%s via port %s"
                  real-imap-server real-imap-server-port nnimap-server-port)
         (let (done)
           (with-current-buffer imap-tunnel-buffer
             (erase-buffer)
             (when (progn
                     (setq imap-tunnel-process
                           (start-process "imap-tunnel" imap-tunnel-buffer
                                          shell-file-name shell-command-switch
					  ;; Cygwin ssh doesn't really work here, so we use PuTTY instead
                                          (format "c:\\PROGRA~1\\PuTTY\\plink.exe -l edi -i c:\\home\\.ssh\\edi.ppk -L %s:localhost:%s %s"
                                                  nnimap-server-port real-imap-server-port real-imap-server)))
                     (process-kill-without-query imap-tunnel-process)
                     imap-tunnel-process)
               (while (and (memq (process-status imap-tunnel-process) '(open run))
                           (goto-char (point-min))
                           (not (looking-at ".")))
                 (accept-process-output imap-tunnel-process 1)
                 (sit-for 1))
               (setq done t)))
           (message "Creating imap tunnel through %s:%s via port %s...%s"
                    real-imap-server real-imap-server-port nnimap-server-port
                    (if done "done" "failed"))))))

(defadvice imap-open (before imap-tunnel () activate)
  (maybe-create-imap-tunnel))

(add-hook 'gnus-after-exiting-gnus-hook 'maybe-destroy-imap-tunnel)
In my ~/.authinfo I had to add this line:
  machine localhost login edi password frob port 7777
And finally, make sure all messags I've read (IMAP or NNTP) are cached locally so I have them available when I'm offline:
(setq gnus-agent-cache t
      gnus-agent-consider-all-articles t
      gnus-agent-enable-expiration 'DISABLE
      gnus-select-article-hook 'gnus-agent-fetch-selected-article)

IRC

I sometimes have to use IRC for work and I think that ERC is the most convenient client when you're already using Emacs. Setting up ERC to work over SSL is a bit of a nuisance on Windows, though. I've managed to connect to the servers which are relevant to me, but I had problems with other servers which I couldn't solve. Anyway, here's the deal: Now you should be able to connect using Emacs commands like (erc-lisp).
 

Acknowledgements

Most of the stuff presented here was collected from various places on the Web or on Usenet. My thanks to all (too numerous to mention individually) who provided this helpful information. Special thanks to the Emacs and Gnus hackers. Any errors are mine, of course.

$Header: /usr/local/cvsrep/weitz.de/win/index.html,v 1.11 2011-01-27 19:41:46 edi Exp $

BACK TO MY HOMEPAGE