<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://stebalien.com/"><id>https://stebalien.com/blog/</id><title>Stebalien's Blog</title><link href="https://stebalien.com/blog/"/><link rel="self" href="blog/atom.xml"/><updated>2025-10-07T15:22:34.031617054+00:00</updated><icon>assets/icon-7fd345408aeae68f.png</icon><author><name>Steven Allen</name><email>steven@stebalien.com</email></author><entry><id>https://stebalien.com/blog/replacing-git-link-with-forge/</id><title>Replacing Git-Link with Forge</title><link href="https://stebalien.com/blog/replacing-git-link-with-forge/" rel="alternate"/><updated>2025-10-06T22:13:45+00:00</updated><published>2025-10-06T22:13:45+00:00</published><content type="html">&lt;p&gt;I recently learned that, as of v0.4.7, &lt;a href=&quot;https://github.com/magit/forge&quot;&gt;Forge&lt;/a&gt;’s &lt;code&gt;forge-copy-url-at-point-as-kill&lt;/code&gt; command works on files and regions (like &lt;a href=&quot;https://github.com/sshaw/git-link&quot;&gt;git-link&lt;/a&gt;). That is, when visiting a file inside a git repo, &lt;code&gt;forge-copy-url-at-point-as-kill&lt;/code&gt; will copy a (web) link to the current file if the region isn’t active and will copy a permalink to the selected lines if the region &lt;em&gt;is&lt;/em&gt; active. This completely replaces the git-link package for me.&lt;/p&gt;&lt;p&gt;Even better, because it’s a part of the &lt;a href=&quot;https://github.com/magit/magit&quot;&gt;Magit&lt;/a&gt; family:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;It can copy links to commits in Magit’s blame buffers, log buffers, etc.&lt;/li&gt;&lt;li&gt;When visiting a file at a specific commit (e.g., by pressing RET on a diff hunk or a Magit blame section), it copies links at the commit in question.&lt;/li&gt;&lt;li&gt;It works in Magit status buffers, etc.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The downside is that it doesn’t integrate with VC, but if you’re using Magit that’s not going to be an issue for you.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/email-actions-in-emacs-notmuch/</id><title>Email actions in Emacs (notmuch)</title><link href="https://stebalien.com/blog/email-actions-in-emacs-notmuch/" rel="alternate"/><updated>2025-08-26T03:38:02+00:00</updated><published>2025-08-26T03:38:02+00:00</published><content type="html">&lt;p&gt;A few years ago I wrote &lt;a href=&quot;https://stebalien.com/projects/microdata-el/&quot;&gt;microdata.el&lt;/a&gt;, an Emacs package for extracting and acting on microdata in email. Since then, I’ve been using it daily to open Discourse and GitHub PR/issue notifications from notmuch with a single keypress (&lt;code&gt;C-c C-c&lt;/code&gt;) instead of having to find and click on the appropriate link.&lt;/p&gt;&lt;p&gt;Many automated emails include structured metadata using either &lt;a href=&quot;https://json-ld.org/&quot;&gt;JSON-LD&lt;/a&gt; and/or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Microdata&quot;&gt;Microdata&lt;/a&gt;. Adoption isn’t great, but code forges like GitLab and GitHub use this to include metadata in their notifications indicating how to view the associated issue/PR/etc. in the browser.&lt;/p&gt;&lt;p&gt;This package provides commands for reading and acting on this metadata.&lt;/p&gt;&lt;h1&gt;Configuration&lt;/h1&gt;&lt;p&gt;You can install the package with the built-in &lt;code&gt;use-package-vc&lt;/code&gt; (or straight, or Elpaca, etc.). I’ve never bothered to publish the package to MELPA so you’ll need to directly fetch it via git.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-elisp&quot;&gt;(use-package microdata
  :vc (:url &amp;quot;https://github.com/Stebalien/microdata.el&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I distribute a notmuch integration library along with the package. However, there’s no reason it wouldn’t work with &lt;a href=&quot;https://www.djcbsoftware.nl/code/mu/mu4e.html&quot;&gt;mu4e&lt;/a&gt; or &lt;a href=&quot;https://www.gnus.org/&quot;&gt;Gnus&lt;/a&gt; (but you’ll have to write that integration yourself).&lt;/p&gt;&lt;p&gt;For notmuch, you can set it up like this (assuming you already have the &lt;code&gt;microdata&lt;/code&gt; package itself):&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-elisp&quot;&gt;(use-package notmuch-microdata
  :bind
  (:map notmuch-show-mode-map
  &amp;quot;C-c C-c&amp;quot; 'notmuch-microdata-show-action-view
  :map notmuch-search-mode-map
  &amp;quot;C-c C-c&amp;quot; 'notmuch-microdata-search-action-view))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this configuration, pressing &lt;code&gt;C-c C-c&lt;/code&gt; in the notmuch search view or when viewing an email will open the default action URL (typically the issue, PR, or forum thread) in your browser.&lt;/p&gt;&lt;h1&gt;Matching GMail&lt;/h1&gt;&lt;p&gt;Unfortunately, this package will never be able to compete with GMail’s built-in actions feature. Given recent advances in language models, applications like GMail simply read the email itself so there’s little pressure for senders to standardize on structured metadata in standard formats.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/dim-minibuffer-prompt-when-unfocused/</id><title>Dim Unfocused Minibuffer Prompts</title><link href="https://stebalien.com/blog/dim-minibuffer-prompt-when-unfocused/" rel="alternate"/><updated>2025-09-26T19:00:00+00:00</updated><published>2025-07-15T03:38:15+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Emacs 31 will ship with a similar feature to the one described below called &lt;code&gt;minibuffer-nonselected-mode&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I can easily tell if an &lt;a href=&quot;https://stebalien.com/tags/emacs&quot;&gt;Emacs&lt;/a&gt; buffer is focused by looking at the buffer’s mode-line. On the other hand, the only indicator that the minibuffer is focused or not is the cursor, and that’s pretty easy to miss.&lt;/p&gt;&lt;p&gt;I wanted to avoid making any intrusive changes to the minibuffer, so I decided to style the prompt based on whether the minibuffer is focused. When &lt;em&gt;not&lt;/em&gt; focused, I remap the minibuffer prompt face to &lt;code&gt;shadow&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Focused:
&lt;img src=&quot;https://stebalien.com/blog/dim-minibuffer-prompt-when-unfocused/static/focused.png&quot; alt=&quot;focused minibuffer&quot;&gt;&lt;/p&gt;&lt;p&gt;Unfocused:
&lt;img src=&quot;https://stebalien.com/blog/dim-minibuffer-prompt-when-unfocused/static/unfocused.png&quot; alt=&quot;unfocused minibuffer&quot;&gt;&lt;/p&gt;&lt;p&gt;The code is pretty simple:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-elisp&quot;&gt;(defvar-local steb/focused-minibuffer-face-remap nil
  &amp;quot;Face-remapping for a dimmed-minibuffer prompt.&amp;quot;)

(defun steb/focused-minibuffer-update (w)
  (when (eq w (minibuffer-window))
    (when steb/focused-minibuffer-face-remap
      (face-remap-remove-relative steb/focused-minibuffer-face-remap)
      (setq steb/focused-minibuffer-face-remap nil))
    (unless (eq w (selected-window))
      (with-selected-window (minibuffer-window)
        (setq steb/focused-minibuffer-face-remap
              (face-remap-add-relative 'minibuffer-prompt 'shadow))))))

(defun steb/minibuffer-setup-focus-indicator ()
  (add-hook 'window-state-change-functions 'steb/focused-minibuffer-update nil t))

(add-hook 'minibuffer-setup-hook #'steb/minibuffer-setup-focus-indicator)
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;&lt;p&gt;My approach was heavily inspired by &lt;a href=&quot;https://emacs.stackexchange.com/questions/73783/change-minibuffer-color-when-not-in-minibuffer-and-minibuffer-active/73785#73785&quot;&gt;this Emacs Stack Exchange answer&lt;/a&gt;, but I tried to simplify the implementation and make the styling a bit less intrusive.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/grammar-tools-in-emacs/</id><title>Grammar Tools in Emacs</title><link href="https://stebalien.com/blog/grammar-tools-in-emacs/" rel="alternate"/><updated>2025-07-14T14:35:34+00:00</updated><published>2025-07-14T14:35:34+00:00</published><content type="html">&lt;p&gt;Up till now the only prose checker/linter I’ve used in Emacs is &lt;a href=&quot;https://github.com/minad/jinx&quot;&gt;jinx&lt;/a&gt;. It’s an amazing spell checker for programmers and technical writers: it handles &lt;code&gt;snake_case&lt;/code&gt; and &lt;code&gt;CamelCase&lt;/code&gt; symbols, knows what to spell-check based on the text’s face, etc. Unfortunately, it’s &lt;em&gt;just&lt;/em&gt; a spell checker and won’t catch any grammatical errors.&lt;/p&gt;&lt;p&gt;Now I’m looking for a local (offline) grammar checker. Something to catch stupid mistakes: repeat words, cut off sentences, tense disagreements, homonyms, etc. I tend to jump around a lot when writing and want a tool that’ll tell me when I started a sentence in one tense and finished it in another, or failed to finish it entirely. Ideally the tool would also provide general writing advice — wording improvements, etc. — but I’ll probably need a full language model for that.&lt;/p&gt;&lt;h1&gt;Vale&lt;/h1&gt;&lt;p&gt;I first tried &lt;a href=&quot;https://vale.sh/&quot;&gt;Vale&lt;/a&gt; because it’s by far the simplest option and has great support for markup languages and can even lint prose within source code. To be honest, it looks like a great choice for teams that want custom rule-based prose linting. Its primary power is the expressivity of its rules allowing organizations to set and enforce writing standards (e.g., “don’t start a sentence with ‘but’”). Unfortunately, it’s not a general-purpose grammar checker, and I’m not trying to conform to some specific set of writing stylistic rules.&lt;/p&gt;&lt;p&gt;However, if that’s what you want, vale has great Emacs integration via &lt;a href=&quot;https://github.com/tpeacock19/flymake-vale&quot;&gt;flymake-vale&lt;/a&gt;. I say great because (a) it uses flymake (no language server setup required) and (b) only checks changed text making it very fast and lightweight.&lt;/p&gt;&lt;h1&gt;Harper&lt;/h1&gt;&lt;p&gt;Next I tried &lt;a href=&quot;https://writewithharper.com/&quot;&gt;Harper&lt;/a&gt;. Like Vale, it’s mostly rule-based. However, it’s more focused on providing general-purpose writing tips than on enforcing specific stylistic rules: it can catch repeat words and tell me when a sentence is too long; it has some very basic grammar rules and can catch article/noun disagreements (a/an); it even has some rules to catch “it’s” versus “its”. I hate it.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Harper thinks “given its computed value” should be “given it’s computed value”.&lt;/li&gt;&lt;li&gt;Harper won’t catch errors like “Go to a the park.”&lt;/li&gt;&lt;li&gt;Harper likes to harp on “mistakes” like “long” sentences.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Harper is like that middle-school english teacher with their highly rigid rules about writing: great for teaching good habits to the notice writer but, at this point, I’ve ingrained them. When I break these rules, it’s (usually) because I &lt;em&gt;meant&lt;/em&gt; to break them.&lt;/p&gt;&lt;p&gt;What I need is a tool to help me catch stupid errors and/or a tool to help improve the flow of my writing. Harper is not that tool.&lt;/p&gt;&lt;p&gt;Worse, the only Emacs integration I found was &lt;code&gt;harper-ls&lt;/code&gt;, a language server. In Emacs specifically, this has a few downsides:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Eglot (the default language server package in Emacs) only supports a single language server per buffer, so I can’t use Harper along with some other language server.&lt;/li&gt;&lt;li&gt;Eglot expects Language Servers to be used in “projects” and will enumerate all project files when it starts the language server. Starting a language server in my home directory takes forever as it tries to enumerate all my files (including my backup snapshots…).&lt;/li&gt;&lt;li&gt;Eglot will start one language server per project (in this case, often per directory).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;However, if you &lt;em&gt;really&lt;/em&gt; want to use Harper, you can trick Eglot into treating all documents (with the same major mode) managed by harper as if they were in the same project. If you don’t care, skip to the next section.&lt;/p&gt;&lt;p&gt;First you’ll need to define a new project type for harper. In my case, I “root” the project in a guaranteed empty directory.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-elisp&quot;&gt;(cl-defmethod project-root ((project (eql harper)))
  &amp;quot;/var/empty&amp;quot;)

(cl-defmethod project-name ((project (eql harper)))
  &amp;quot;harper&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This virtual “harper” project contains all files managed by the harper language server. Honestly, it’s probably OK to simply return &lt;code&gt;nil&lt;/code&gt; here.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-elisp&quot;&gt;(cl-defmethod project-files ((project (eql harper)))
  (when-let* ((server (cl-find-if #'eglot--languageId
                                  (gethash (eglot--current-project)
                                           eglot--servers-by-project))))
    (mapcar #'buffer-file-name (eglot--managed-buffers server))))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, add a project “finder” function that puts all buffers that &lt;em&gt;would&lt;/em&gt; be managed by harper into the “harper” project, if and only if we’re acting on behalf of Eglot (&lt;code&gt;eglot-lsp-context&lt;/code&gt; is non-nil).&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-elisp&quot;&gt;(defun project-try-harper (dir)
  (and eglot-lsp-context
       (string= (cadr (eglot--lookup-mode major-mode)) &amp;quot;harper-ls&amp;quot;)
       'harper))

(add-to-list 'project-find-functions #'project-try-harper)
&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;LanguageTool&lt;/h1&gt;&lt;p&gt;Finally, I broke down and set up &lt;a href=&quot;https://languagetool.org/&quot;&gt;LanguageTool&lt;/a&gt;: it’s by far the most advanced free-software grammar tool. The next step-up is Grammarly and/or LanguageTool’s online offering, but I absolutely refuse to use a remote grammar tool (I’m not sending all my notes, etc. to someone else’s server).&lt;/p&gt;&lt;p&gt;Unfortunately, it’s definitely a heavyweight. It uses at least 2.5GiB of memory, and you’ll need the 14GiB &lt;a href=&quot;https://languagetool.org/download/ngram-data/&quot;&gt;n-gram database&lt;/a&gt; if you really want it to do its job. On the other hand, it’s leagues beyond Harper and Vale in terms of catching grammatical mistakes.&lt;/p&gt;&lt;p&gt;In terms of Emacs integration, I ended up creating a &lt;a href=&quot;https://github.com/Stebalien/flymake-languagetool&quot;&gt;fork&lt;/a&gt; of &lt;a href=&quot;https://github.com/emacs-languagetool/flymake-languagetool&quot;&gt;&lt;code&gt;flymake-languagetool&lt;/code&gt;&lt;/a&gt; that has improved performance (avoids re-checking the entire document on change); excludes markup, code, etc. before sending text to LanguageTool instead of filtering errors afterward for improved accuracy/performance; and renders diagnostics in the correct location even when Emoji are present in the buffer. I considered using a language server, but I had enough of that with Harper.&lt;/p&gt;&lt;p&gt;I recommend you find a good LanguageTool docker container or something because setting it up from scratch isn’t a fun exercise. However, if you’re like me and (a) use Arch Linux and (b) avoid sketchy docker containers, read on.&lt;/p&gt;&lt;p&gt;On Arch, I installed the &lt;a href=&quot;https://archlinux.org/packages/extra/any/languagetool/&quot;&gt;&lt;code&gt;languagetool&lt;/code&gt;&lt;/a&gt; package from the extra repository (pick &lt;code&gt;jre21-openjdk-headless&lt;/code&gt; when prompted), along with a few AUR packages: &lt;a href=&quot;https://aur.archlinux.org/packages/languagetool-ngrams-en&quot;&gt;&lt;code&gt;languagetool-ngrams-en&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://aur.archlinux.org/packages/fasttext&quot;&gt;&lt;code&gt;fasttext&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&quot;https://aur.archlinux.org/packages/fasttext-langid-models&quot;&gt;&lt;code&gt;fasttext-langid-models&lt;/code&gt;&lt;/a&gt;. The fasttext packages are only used for language detection so they should be optional, but LanguageTool’s HTTP server complains if they’re not installed.&lt;/p&gt;&lt;p&gt;Next, you’ll need to create a configuration file to tell LanguageTool where to find everything:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-properties&quot;&gt;fasttextModel=/usr/share/fasttext/lid.176.bin
fasttextBinary=/usr/bin/fasttext
languageModel=/usr/share/ngrams
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You’ll need to pass &lt;code&gt;--config /path/to/my/config.properties&lt;/code&gt; to the &lt;code&gt;languagetool&lt;/code&gt; command (which you can configure via &lt;code&gt;flymake-languagetool-server-command&lt;/code&gt; and &lt;code&gt;flymake-languagetool-server-args&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-elisp&quot;&gt;(setopt flymake-languagetool-server-command &amp;quot;languagetool&amp;quot;
        flymake-languagetool-server-args
        '(&amp;quot;--http&amp;quot; &amp;quot;--allow-origin&amp;quot; &amp;quot;*&amp;quot;
          &amp;quot;--config&amp;quot; &amp;quot;/path/to/my/config.properties&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That’ll get you a basic functioning LanguageTool server.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/emacs-side-windows/</id><title>Taming Org-Mode With Side Windows</title><link href="https://stebalien.com/blog/emacs-side-windows/" rel="alternate"/><updated>2025-07-03T17:31:42+00:00</updated><published>2025-07-03T17:31:42+00:00</published><content type="html">&lt;p&gt;Org-mode will, occasionally, pop up a window that takes over half the screen. Specifically, this happens to when:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Capturing a note (&lt;code&gt;org-capture&lt;/code&gt;): I get a half-screen window asking me to select the capture template.&lt;/li&gt;&lt;li&gt;Inserting a timestamp (&lt;code&gt;org-timestamp&lt;/code&gt;): The calendar takes over half the screen (assuming &lt;code&gt;org-read-date-popup-calendar&lt;/code&gt; is non-nil).&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This is especially annoying in the second case because the calendar window invariably covers the window I’m referencing while taking notes. That is, I usually take notes with the following setup where the red window is the selected window and the gray window is some document I’m referencing.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://stebalien.com/blog/emacs-side-windows/static/note-windows.svg&quot; alt=&quot;reference &amp;amp; notes windows side-by-side&quot;&gt;&lt;/p&gt;&lt;p&gt;In this case, attempting to insert a timestamp in my notes causes a calendar window to pop-up, covering the reference window:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://stebalien.com/blog/emacs-side-windows/static/note-windows-calendar.svg&quot; alt=&quot;calendar window replacing the reference window&quot;&gt;&lt;/p&gt;&lt;p&gt;The solution is to use &lt;a href=&quot;https://www.gnu.org/software/emacs/manual/html_node/elisp/Displaying-Buffers-in-Side-Windows.html&quot; id=&quot;side-windows&quot;&gt;side windows&lt;/a&gt;. E.g., I can put the calendar at the top of the screen above the notes/reference windows, replacing neither.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://stebalien.com/blog/emacs-side-windows/static/note-windows-side-calendar.svg&quot; alt=&quot;calendar window above the notes &amp;amp; reference windows&quot;&gt;&lt;/p&gt;&lt;p&gt;First, put calendar windows in a dedicated side-window at the top of the screen.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-elisp&quot;&gt;(setf (alist-get (rx bos &amp;quot;*Calendar*&amp;quot; eos) display-buffer-alist nil nil #'equal)
      '(display-buffer-in-side-window (side . top) (dedicated . t)
                                      (window-height . fit-window-to-buffer)))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, put org-capture template selection buffers in a side-window at the &lt;em&gt;bottom&lt;/em&gt; of the screen, instead of taking over half the screen.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-elisp&quot;&gt;(setf (alist-get (rx bos &amp;quot;*Org Select* eos) display-buffer-alist #nil nil 'equal)
      '(display-buffer-in-side-window (dedicated . t)))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you want to better understand Emacs window management, I highly recommend Mickey’s (of Mastering Emacs) excellent write up: &lt;a href=&quot;https://www.masteringemacs.org/article/demystifying-emacs-window-manager&quot; id=&quot;mastering-emacs&quot;&gt;Demystifying Emacs’s Window Manager&lt;/a&gt;.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/org-mode-executable/</id><title>Executable Org-Mode Files</title><link href="https://stebalien.com/blog/org-mode-executable/" rel="alternate"/><updated>2025-05-30T03:16:16+00:00</updated><published>2025-05-30T03:16:16+00:00</published><content type="html">&lt;p&gt;My Emacs config lives in a massive org-mode file and I often want to re-&lt;a href=&quot;https://orgmode.org/manual/Extracting-Source-Code.html&quot;&gt;tangle&lt;/a&gt; from outside Emacs (because, e.g., I broke my Emacs session for some reason). I could just use a simple &lt;code&gt;Makefile&lt;/code&gt;, but that’s no fun. Instead, I’ve turned my config into a self-tangling script by adding the following two lines to the top of my &lt;code&gt;init.org&lt;/code&gt; file and marking it as executable:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-org&quot;&gt;#!/usr/bin/env -S sh -c 'emacs --batch --eval=&amp;quot;(setq org-confirm-babel-evaluate nil)&amp;quot; --file &amp;quot;$0&amp;quot; -f org-babel-tangle'
# -*- mode: org; lexical-binding: t -*-
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now I can run &lt;code&gt;./init.org&lt;/code&gt; in my &lt;code&gt;~/.emacs.d&lt;/code&gt; directory and my config will tangle itself into the correct files.&lt;/p&gt;&lt;p&gt;This might sound like a weird gimmick – to be honest, it kind of is – but I’ve been tangling my config this way for years at this point and it “just works” with no dependencies other than Emacs.&lt;/p&gt;&lt;hr&gt;&lt;p&gt;A similar trick can be used to execute all org-babel blocks in an org-mode file, making org-mode a viable meta-scripting language for tying together scripts written in different languages – even scripts executing across multiple machines via &lt;a href=&quot;https://www.gnu.org/software/tramp/&quot;&gt;TRAMP&lt;/a&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-org&quot;&gt;#!/usr/bin/env -S sh -c 'emacs --batch --eval=&amp;quot;(setq org-confirm-babel-evaluate nil)&amp;quot; --file &amp;quot;$0&amp;quot; -f org-babel-execute-buffer'
# -*- mode: org; lexical-binding: t -*-
#+TITLE: Hello World

#+BEGIN_SRC elisp
(message &amp;quot;Hello World!&amp;quot;)
#+END_SRC
&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><id>https://stebalien.com/blog/letters-from-a-maintainer/</id><title>Letters From A Maintainer</title><link href="https://stebalien.com/blog/letters-from-a-maintainer/" rel="alternate"/><updated>2021-01-28T00:00:00+00:00</updated><published>2021-01-28T00:00:00+00:00</published><summary type="text">This blog post is a series of letters from me, an OSS maintainer, to anyone contributing code to OSS projects. My goal is condense haphazard advice into a single document and help contributors understand a maintainer’s perspective on OSS.</summary><content type="html">&lt;p&gt;This blog post is a series of letters from me, an OSS maintainer, to anyone contributing code to OSS
projects. My goal is condense haphazard advice into a single document and help contributors
understand a maintainer’s perspective on OSS.&lt;/p&gt;&lt;p&gt;For some context, I’ve been an OSS maintainer of &lt;a href=&quot;https://github.com/ipfs/go-ipfs/&quot;&gt;go-ipfs&lt;/a&gt; and
&lt;a href=&quot;https://github.com/libp2p/go-libp2p/&quot;&gt;go-libp2p&lt;/a&gt; for over 3 years now, along with some personal
projects including &lt;a href=&quot;https://github.com/Stebalien/tempfile/&quot;&gt;tempfile (rust)&lt;/a&gt;, and &lt;a href=&quot;https://github.com/Stebalien/horrorshow-rs/&quot;&gt;horrorshow
(rust)&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;These letters mostly draw on my experiences maintaining go-ipfs and go-libp2p. While they should be
generally applicable, they’re most applicable to systems programming in go.&lt;/p&gt;&lt;h1&gt;Contributing&lt;/h1&gt;&lt;p&gt;If every stumbled on a new project and wanted to help out but didn’t know where to start? This is
for you.&lt;/p&gt;&lt;h2&gt;Do: Triage issues, new and old&lt;/h2&gt;&lt;p&gt;Triaging issues, new and old, is time consuming. Any amount of time a contributor can commit to this
is helpful as the task often falls to the project’s maintainers:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Reproducing old issues to trim down the backlog.&lt;/li&gt;&lt;li&gt;Asking clarifying questions on new issues.&lt;/li&gt;&lt;li&gt;Finding and calling out duplicates.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Do: Help on the forums (and IRC)&lt;/h2&gt;&lt;p&gt;You don’t need a deep understanding of a project to start answering questions on a project’s forums.
Most questions can be answered with a bit of searching. The difference between you and the person
asking the question is that you have confidence in your ability to find the answers yourself.&lt;/p&gt;&lt;p&gt;Additionally, that bit of searching will teach you about the project and open up additional avenues
for contributing to the project.&lt;/p&gt;&lt;h2&gt;Do: Add Documentation/Examples&lt;/h2&gt;&lt;p&gt;Again, this is a great way to start learning about a project as it forces you to learn how it works.&lt;/p&gt;&lt;h2&gt;Do: Add Tests&lt;/h2&gt;&lt;p&gt;Nobody likes writing tests. Unfortunately, this means that tests are one of the things many projects
need most. If you want to start &lt;em&gt;coding&lt;/em&gt; on a new project immediately and want to better understand
how its internals work, one of the easiest ways is to start writing new tests.&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Look through the code for some example tests.&lt;/li&gt;&lt;li&gt;Look through the issue tracker for issues labeled “needs test”.&lt;/li&gt;&lt;li&gt;Use code coverage analysis tools to find areas of the code that are poorly tested.&lt;/li&gt;&lt;li&gt;Open an issue tracking which areas you plan on testing. This gives the maintainer a chance to
give you feedback and suggestions before you start actually coding.&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;Do: Fix Bugs&lt;/h2&gt;&lt;p&gt;Go through bugs marked “good first issue” and/or “help wanted” and start fixing them. Unless the
project is rigorous about project management, it’s probably a good idea to leave a quick comment
asking if you should fix the bug just in case the labels are out of date.&lt;/p&gt;&lt;p&gt;Honestly, many issues in OSS projects are small and just need someone to spend a few hours debugging
them. However, these kinds of issues tend to pile up into an mountain that nobody wants to climb.&lt;/p&gt;&lt;h2&gt;Don’t: Airdrop (large) Patches&lt;/h2&gt;&lt;p&gt;I know you really want that amazing feature. However, that amazing feature may not fit in with the
rest of the project and may conflict other in-flight endeavors. Read &lt;a href=&quot;https://stebalien.com/#writing-code-for-review&quot;&gt;Writing Code For
Review&lt;/a&gt; before submitting patches.&lt;/p&gt;&lt;h2&gt;Don’t: Spam Ideas&lt;/h2&gt;&lt;p&gt;We’re all guilty of this. We see a shiny new project and have so many ideas about how it could be
improved! Concrete feature requests with concrete designs are awesome! However, if you’re new to a
project, that’s probably not the best place to start.&lt;/p&gt;&lt;p&gt;A deluge of “I think this project should do X” requests are quite frustrating for maintainers.
Unfortunately, your idea &lt;em&gt;probably&lt;/em&gt; isn’t new to the maintainer. By raising it in an issue, the
maintainer now needs to address the issue. Addressing and managing expectations (i.e., “we don’t
have time for that right now”) around half-baked ideas is really time consuming.&lt;/p&gt;&lt;p&gt;If you’ve ever written up a long, revolutionary issue on how a project could improve by 10x and
found yourself completely ignored, this is probably why.&lt;/p&gt;&lt;p&gt;If you have an idea, write up a &lt;em&gt;short&lt;/em&gt; post on the project’s forum (if it exists). This makes it
clear that the idea is for discussion and that there are no expectation that any maintainers and/or
devs will implement and/or respond any time soon.&lt;/p&gt;&lt;p&gt;Note: This advice really depends on the project size and activity. For small projects with little
activity, a thoughtful feature request here and there can very be welcome.&lt;/p&gt;&lt;h2&gt;Don’t: Hijack&lt;/h2&gt;&lt;p&gt;Please be &lt;em&gt;very&lt;/em&gt; careful not to hijack a discussion. If you think you have a similar idea/bug as an
existing issue but aren’t sure, just open a new issue. Unfortunately, popular OSS collaboration
tools like GitHub don’t support threaded conversations. This means any off topic discussion will
stick out as a large blob of confusion in the middle of a long technical discussion.&lt;/p&gt;&lt;h1&gt;Code For Humans&lt;/h1&gt;&lt;p&gt;While there are many techniques for structuring readable code, the three I find most useful when
reviewing are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Modularity/Composability&lt;/li&gt;&lt;li&gt;Isolation&lt;/li&gt;&lt;li&gt;Good Abstractions&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The best way to learn how to write good code is to read code. Read &lt;em&gt;lots&lt;/em&gt; of code, both good and
bad. Writing code is not a substitute.&lt;/p&gt;&lt;p&gt;Note: This post is not a substitute for reading a good book on software architecture or taking a
class. Please take the time to do so.&lt;/p&gt;&lt;h2&gt;Modularity/Composability&lt;/h2&gt;&lt;p&gt;When a system is built of smaller components, the reader can independently understand each piece in
isolation and how they fit together instead of having to understand the entire system all at once.&lt;/p&gt;&lt;p&gt;This sounds obvious because it is. The non-obvious part is how to put this into practice. The main
take away is: modular designs &lt;em&gt;are&lt;/em&gt; worth the effort; if you find yourself working on a large, messy
service, try breaking it into smaller pieces.&lt;/p&gt;&lt;p&gt;On the other hand, modularity &lt;em&gt;can&lt;/em&gt; make things &lt;em&gt;more&lt;/em&gt; complicated by introducing indirection. Be
careful: If you have co-dependent services (services that know about and/or call into each other
frequently), consider merging them.&lt;/p&gt;&lt;p&gt;The best way to avoid this is to clearly define a component’s responsibility up-front and define the
interfaces between components as early as possible (ideally in a design document before you even
begin coding).&lt;/p&gt;&lt;h2&gt;Isolation&lt;/h2&gt;&lt;p&gt;Independent subsystems should behave and/or fail independently. For example, if two subsystems use a
&lt;em&gt;shared&lt;/em&gt; service, and one of these subsystems cancels a request, this cancellation shouldn’t affect
the other subsystem.&lt;/p&gt;&lt;p&gt;This rule is never up for debate as interference between seemingly isolated services is extremely
difficult to debug. Always decide and document up-front whether a service/component is &lt;em&gt;shared&lt;/em&gt; or
&lt;em&gt;owned&lt;/em&gt;.&lt;/p&gt;&lt;h2&gt;Good Abstractions&lt;/h2&gt;&lt;p&gt;Abstractions/APIs are the UX of programming. The actual behavior of an abstraction/API must conform
to the user’s expectation of that abstraction/API. Unfortunately, this is mostly subjective.&lt;/p&gt;&lt;p&gt;Advice:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;&lt;strong&gt;Read &lt;em&gt;tons&lt;/em&gt; of code&lt;/strong&gt;. The more code you read, the more abstractions/idioms you’ll see and the
better code you’ll write.&lt;/li&gt;&lt;li&gt;Use your code. This is the fastest way to find pain-points and affordance mismatches.&lt;/li&gt;&lt;li&gt;Take advice. If an experienced user thinks something is funky with your abstraction, pay
attention. They’re your user.&lt;/li&gt;&lt;li&gt;Explain your abstractions to a rubber duck.&lt;/li&gt;&lt;li&gt;If you have to &lt;em&gt;break&lt;/em&gt; your abstraction to access internals, it’s probably wrong or at least
sub-optimal.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A close method should close and cleanup all resources.&lt;/li&gt;&lt;li&gt;A close method should generally be idempotent (depends on the language) simply because it &lt;em&gt;usually
is&lt;/em&gt; (and is therefore expected).&lt;/li&gt;&lt;li&gt;In go, if a function returns a value and an error, the caller should be able to bubble the error
up the chain and walk away. Specifically, the caller &lt;em&gt;should not&lt;/em&gt; have to do anything with the
value (e.g., close it).&lt;/li&gt;&lt;li&gt;If your code looks like it follows a well-known abstraction (e.g., one of the Go interfaces,
etc.), it should actually &lt;em&gt;conform&lt;/em&gt; to that abstraction. Familiarize yourself with your
programming language’s standard &amp;amp; common libraries.&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;Breaking Changes&lt;/h1&gt;&lt;p&gt;Breaking changes are changes that require downstream projects to react to changes made upstream.
From least to most severe, IPFS and libp2p have the following interfaces that can “break”:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Compile-time APIs&lt;/li&gt;&lt;li&gt;Datastores/Databases&lt;/li&gt;&lt;li&gt;Network Protocols&lt;/li&gt;&lt;li&gt;Run-time APIs&lt;/li&gt;&lt;li&gt;Data Formats&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;Compile-time APIs&lt;/h2&gt;&lt;blockquote&gt;&lt;p&gt;The go-ipfs Go API.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;While annoying, compile-time API changes are the easiest to deal with as downstream projects usually
either lock (e.g., with go modules) or vendor dependencies.&lt;/p&gt;&lt;p&gt;However,&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;&lt;em&gt;Please&lt;/em&gt; don’t make breaking API changes for aesthetic reasons (unless you’re already breaking
the API for other reasons). These kind of changes are &lt;em&gt;extremely&lt;/em&gt; frustrating to downstream
users.&lt;/li&gt;&lt;li&gt;Explicitly call-out all breaking changes when submitting a patch. These changes need to receive
extra-careful attention, need to be called out in release notes, and may need additional review
from stakeholders.&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;Database/Datastore&lt;/h2&gt;&lt;blockquote&gt;&lt;p&gt;Database schemas.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Changes to datastores/databases can be handled through an automatic migrations. However, while
they’re supposed to be transparent, such migrations often take time and can fail catastrophically if
not implemented correctly. Either avoid these kinds of changes or test your migration thoroughly.&lt;/p&gt;&lt;h2&gt;Network Protocols&lt;/h2&gt;&lt;blockquote&gt;&lt;p&gt;Bitswap, Yamux, etc.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Network-protocols &lt;em&gt;can&lt;/em&gt; evolve over time as long as a deprecation period is provided. However, this
deprecation period gets longer and longer the more mature a system becomes.&lt;/p&gt;&lt;p&gt;The process usually looks like:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Add a new feature.&lt;/li&gt;&lt;li&gt;Wait some time (adoption period).&lt;/li&gt;&lt;li&gt;Enable the new feature by default and deprecate the old feature.&lt;/li&gt;&lt;li&gt;Wait a long time (deprecation period).&lt;/li&gt;&lt;li&gt;Remove the old feature.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Luckily, IPFS and libp2p are not &lt;em&gt;yet&lt;/em&gt; mature enough for this to be a major issue. Users tend to
update regularly so network-protocol deprecation periods can be as short as a few months.&lt;/p&gt;&lt;h2&gt;Run-time APIs&lt;/h2&gt;&lt;blockquote&gt;&lt;p&gt;The IPFS HTTP API or the Linux kernel syscall API.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Changes to run-time APIs are problematic because, unlike compile-time APIs, the end-user is
responsible for dealing with incompatible run-time APIs. That is, the developer deals with
compile-time API changes when compiling/distributing their app. The end-user deals with run-time API
changes when they update their IPFS daemon or their Linux kernel and their app breaks.&lt;/p&gt;&lt;p&gt;Worse, downstream apps are often unmaintained. For compile-time APIs, this isn’t really an issue:
different apps can be compiled against different versions of the same dependency (usually). For
run-time APIs, this is a significant issue as one can usually only have one copy of the runtime
dependency.&lt;/p&gt;&lt;p&gt;This is what Linus is talking about when he says “we don’t break userspace”. The Linux kernel breaks
&lt;em&gt;compile-time&lt;/em&gt; APIs all the time and expects module maintainers to update their code. However,
ancient, unmaintained applications should continue to “just work”.&lt;/p&gt;&lt;h2&gt;Data Formats&lt;/h2&gt;&lt;blockquote&gt;&lt;p&gt;IPLD Formats, UnixFS, etc.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Removing support for old data/file formats is difficult to impossible. When data is structured and
managed by some database, one can use migrations to migrate from one format to another. However,
this doesn’t apply to the general case.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Users will backup files and then try to read them years later. They should be able to do so.&lt;/li&gt;&lt;li&gt;Data format changes are difficult to reconcile with content addressing (used everywhere in IPFS).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Really, it’s the second point here that makes removing support for old data formats (nearly)
impossible. Re-formatting data &lt;em&gt;changes&lt;/em&gt; the content address (at least in IPLD) which would break
links to the data. Basically:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;IPFS doesn’t break links.&lt;/li&gt;&lt;li&gt;IPFS can’t force-migrate data.&lt;/li&gt;&lt;li&gt;IPFS can’t remove support for old data formats.&lt;/li&gt;&lt;/ol&gt;&lt;h1&gt;Parallel Programming &amp;amp; Locks&lt;/h1&gt;&lt;p&gt;Concurrent programming is easy(ish); just take a global lock and try to avoid deadlocking. Parallel
&lt;em&gt;systems&lt;/em&gt; programming is hard because it usually involves multiple complex synchronization
primitives and multiple services all trying to maximally use multiple cores without blocking each
other.&lt;/p&gt;&lt;p&gt;While it &lt;em&gt;can&lt;/em&gt; be learned through practice, I highly suggest that you read a book on the theory
(e.g., &lt;em&gt;The Art of Multiprocessor Programming&lt;/em&gt; by Maurice Herlihy &amp;amp; Nir Shavit). However, if you
only learn one thing, learn this: &lt;strong&gt;locks are not pepper&lt;/strong&gt;. Don’t pepper your code with locks and
expect it to work.&lt;/p&gt;&lt;p&gt;First, we need to understand what a lock &lt;em&gt;does&lt;/em&gt;. While a useful simplification, a lock &lt;em&gt;does not&lt;/em&gt;,
strictly speaking, “protect some variable(s)”.&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;The primary purpose of a lock is to protect a critical section where invariants are violated. For
example, adding a key/value to a map (in Go) takes multiple instructions. Overall, this sequence
of instructions leaves the map in a consistent state. However, the state of the map &lt;em&gt;is not&lt;/em&gt;
consistent while these instructions are being executed. If another thread attempts to read the
map while it’s being updated, the read will try to operate on an invalid data structure and fail
in unpredictable ways.&lt;/li&gt;&lt;li&gt;The secondary purpose of a lock (and most other synchronization primitives) is to prevent certain
optimizations that are valid in a single-threaded context but invalid when observed from a
separate thread. Before attempting to write a parallel program in a new programming language, you
should read that programming language’s memory model (for example, the &lt;a href=&quot;https://golang.org/ref/mem&quot;&gt;go memory
model&lt;/a&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The most important part of using locks and synchronization primitives is to be consistent and
deliberate:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Clearly document thread-safety. Specifically, which functions can be called at the same time and
which can’t.&lt;/li&gt;&lt;li&gt;Never use locks “just in case”. If you don’t know if you need a lock or not, your code is buggy.&lt;/li&gt;&lt;li&gt;When you decide to use a lock, clearly document and consider the invariants maintained by the
lock. For example, in Go, use the &lt;a href=&quot;https://dmitri.shuralyov.com/idiomatic-go#mutex-hat&quot;&gt;mutex
“hat”&lt;/a&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;h1&gt;Writing Code For Review&lt;/h1&gt;&lt;p&gt;This post covers how to take a patch from an idea to a merged feature.&lt;/p&gt;&lt;h2&gt;Before writing code&lt;/h2&gt;&lt;p&gt;Before writing code, sit down and write up an issue describing the change you intend to make.
Up-front discussion in can avoid having to scrap/rewrite/rework code during a review.&lt;/p&gt;&lt;p&gt;Please do this even if your code is perfect and well tested:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;It may interact with other subsystems or in-flight projects that you aren’t aware of.&lt;/li&gt;&lt;li&gt;The feature may simply not be a high enough priority to warrant the maintainers attention.
Reviewing code takes time.&lt;/li&gt;&lt;li&gt;The subsystem you’re modifying may have undocumented errata/bugs that need to be fixed first.
While these issues &lt;em&gt;should&lt;/em&gt; have been documented, should has little impact on reality.&lt;/li&gt;&lt;li&gt;The feature may introduce a significant &lt;em&gt;maintenance&lt;/em&gt; burden. Code is never written and forgotten,
the maintainer will need to move, fix, and update your code as the project changes.&lt;/li&gt;&lt;li&gt;The feature may not fit in with the goals of the project. That doesn’t mean it’s not useful, it
just means it’s out of scope.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Remember, you see the project through the lens of what &lt;em&gt;you&lt;/em&gt; need to achieve &lt;em&gt;your&lt;/em&gt; goals. The
maintainer sees it as a collection of interacting features, interfaces, and subsystems that they’ll
need to maintain into the future.&lt;/p&gt;&lt;h2&gt;Before submitting a patch/PR&lt;/h2&gt;&lt;p&gt;Before submitting a patch/PR, run through the following list.&lt;/p&gt;&lt;h3&gt;Cleanup&lt;/h3&gt;&lt;p&gt;The first step when submitting a patch is to clean it up.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Avoid dead code.&lt;ul&gt;&lt;li&gt;Even when documented as “dead”, it will bit-rot.&lt;/li&gt;&lt;li&gt;When not clearly documented as dead, your reviewer will waste time reviewing it.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Never leave in commented-out code with no explanation of &lt;em&gt;why&lt;/em&gt; it’s commented out. This usually
indicates that some debugging logic got left behind.&lt;/li&gt;&lt;li&gt;Use a linter. For Go, I recommend &lt;a href=&quot;https://github.com/golangci/golangci-lint&quot;&gt;golangci-lint&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Testing&lt;/h3&gt;&lt;p&gt;After cleaning up your code, the next step is to make sure that the remaining code is well tested.
Bugs caught in review waste quite a bit of time.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Test coverage is nice but not the end-all-be-all. Never trust your test coverage and don’t bother
shooting for 100%. You’ll end up wasting time testing every error path instead of stressing the
critical paths.&lt;/li&gt;&lt;li&gt;Systems programming is &lt;em&gt;different&lt;/em&gt;. The concern is rarely “does this function work in isolation”,
it’s usually “does this function/subsystem work when interleaved with everything else?”&lt;ul&gt;&lt;li&gt;Write parallel stress tests. Even if they’re not deterministic, this will catch quite a few
bugs.&lt;/li&gt;&lt;li&gt;If you’re using Go, use the race detector &lt;code&gt;go test -race&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Use it and/or integrate it before asking for review. You’ll likely want to rework your interfaces.&lt;/li&gt;&lt;li&gt;Explain your code to a rubber ducky (or your partner, a child, a wall, a friend (real or
imaginary)). If you can’t easily do this, consider rewriting your code.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; TDD (test driven development) isn’t sufficient for systems programming. You’ll need to go
back and write test-cases that stress your edge-cases and critical paths.&lt;/p&gt;&lt;h3&gt;Documentation&lt;/h3&gt;&lt;p&gt;Once your code has been tested, it’s time to go back through and fill in any documentation you may
have left out along the way. It’s best to document as you go but you should always do an additional
pass before submitting a patch for review.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Document most types and functions.&lt;ul&gt;&lt;li&gt;Even if the type name is obvious to you, it may not be to others.&lt;/li&gt;&lt;li&gt;Small internal or “idiomatic” types/functions don’t necessarily need to be documented.&lt;ul&gt;&lt;li&gt;Private error &amp;amp; result types.&lt;/li&gt;&lt;li&gt;Single-use helper functions.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Be brief. Use short sentences, short paragraphs, and plenty of punctuation and bullet points.
Nobody wants to read an essay.&lt;/li&gt;&lt;li&gt;Small examples go a long way. Write them.&lt;/li&gt;&lt;li&gt;Specify implicit contracts: those not expressed in the types themselves.&lt;ul&gt;&lt;li&gt;Is the function thread-safe? Under what conditions/assumptions?&lt;/li&gt;&lt;li&gt;Can the function be called twice?&lt;/li&gt;&lt;li&gt;Is it idempotent?&lt;/li&gt;&lt;li&gt;Does it take ownership of the parameters?&lt;/li&gt;&lt;li&gt;Can it panic?&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Use comments to sign-post your code and call out interesting quirks.&lt;ul&gt;&lt;li&gt;Comments are great for sectioning long functions. This is especially true for setup functions
and/or request handlers.&lt;/li&gt;&lt;li&gt;If you had to think about something while writing it, it should have a comment explaining your
thoughts.&lt;/li&gt;&lt;li&gt;If you had to fix a non-obvious bug, there should be a comment to prevent someone from making
the same mistake.&lt;/li&gt;&lt;li&gt;If it looks like something can happen but can’t, write that down. You’ll save someone some
head-scratching later.&lt;/li&gt;&lt;li&gt;If you write some code that looks useless, explain why it’s not useless or someone will &lt;a href=&quot;https://research.swtch.com/openssl&quot;&gt;remove
it&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;NEVER&lt;/strong&gt; leave a comment that tells the reader to do/not do something without an explanation.
Even if you’re the only one working on the project, you will almost certainly forget why when you
revisit the code later.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Break It Up&lt;/h3&gt;&lt;p&gt;Finally, when possible, pull small, stand-alone pieces out of large patches and submit them
separately. This allows the reviewer to focus on the meaty changes.&lt;/p&gt;&lt;p&gt;Alternatively, break these separate patches into separate commits and &lt;em&gt;clearly&lt;/em&gt; communicate that the
patch should be read commit by commit.&lt;/p&gt;&lt;p&gt;At the end of the day, this step depends on the style of patch you’re submitting and the project’s
policies. In go-ipfs, for example, cleanup patches with multiple small changes batched into a single
patch-set are usually fine and save everyone time fixing merge conflicts.&lt;/p&gt;&lt;h2&gt;Submitting a PR&lt;/h2&gt;&lt;p&gt;The most important part of submitting a PR/patch is communication.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Clearly explain what’s being changed, why, and how.&lt;/li&gt;&lt;li&gt;Clearly communicate when something is &lt;em&gt;ready&lt;/em&gt; and &lt;em&gt;not ready&lt;/em&gt; for review.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For large changes, leave yourself a self-review.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Call out interesting changes.&lt;/li&gt;&lt;li&gt;Ask questions.&lt;/li&gt;&lt;li&gt;Ask for advice.&lt;/li&gt;&lt;li&gt;Call out alternative solutions that you’ve considered/tried.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For early feedback, submit a WIP patch with &lt;em&gt;specific&lt;/em&gt; questions. WIP “please look, maybe” patches
with no specific questions (code or design) are frustrating for reviewers as it’s impossible to know
what’s done and what’s still WIP.&lt;/p&gt;&lt;h2&gt;After submitting a PR&lt;/h2&gt;&lt;p&gt;Code review is a discussion. It’s the responsibility of both the code author and the reviewer to
work together to either merge or drop the patch.&lt;/p&gt;&lt;h3&gt;Scope Creep and MVP&lt;/h3&gt;&lt;p&gt;First, only you can prevent scope creep: It’s perfectly acceptable (and good!) to say “let’s punt
that to a later PR”.&lt;/p&gt;&lt;p&gt;However, bugs, data races, potential DoS vectors, etc. are &lt;em&gt;never&lt;/em&gt; acceptable. MVP means minimum
&lt;em&gt;viable&lt;/em&gt; product. Viable means &lt;em&gt;bug free&lt;/em&gt; and &lt;em&gt;useful&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;On the other hand, explicitly unimplemented and/or partially implemented features &lt;em&gt;are&lt;/em&gt; OK. However,
a patch must never include a component that looks done but isn’t and, critically, should never
reduce the stability of the program as a whole. Unimplemented/unfinished features that aren’t
documented as such are &lt;em&gt;bugs&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;While partially implementing a feature, you may need to create a new interface that you &lt;em&gt;know&lt;/em&gt;
you’ll need to extend in the near future. When possible, make interfaces
&lt;a href=&quot;https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis&quot;&gt;extensible&lt;/a&gt; from the
start. When not, clearly &lt;em&gt;document&lt;/em&gt; these interfaces as experimental/partial. Unfortunately, this
isn’t always enough and the maintainer may ask you to finish the feature up-front.&lt;/p&gt;&lt;p&gt;Finally, don’t be a moving target. You will likely realize a better way to do something after
submitting a PR. However, be careful to not repeatedly rewrite an in-progress PR as the previous
review cycles will have been wasted.&lt;/p&gt;&lt;h3&gt;Communication&lt;/h3&gt;&lt;p&gt;You may have noticed the theme of this post: communicate clearly.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Clearly communicate when something is ready for re-review.&lt;/li&gt;&lt;li&gt;Clearly communicate when a review comment has been addressed.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;A Two-Way Street&lt;/h3&gt;&lt;p&gt;Review is a two-way street. The reviewer’s job is to find problems; it’s the author’s job to find
and propose solutions. A good reviewer/maintainer will offer potential solutions and insights but
don’t make your reviewer do all the heavy lifting.&lt;/p&gt;&lt;p&gt;This is especially true if you’re implementing functionality that the maintainer hasn’t explicitly
requested. If you make the maintainer figure out how to plug the feature &lt;em&gt;you&lt;/em&gt; need into the rest of
the application, your patch will sit at the bottom of the pile until the maintainer has enough free
time to figure it out (possibly indefinitely).&lt;/p&gt;&lt;p&gt;Finally, the less work a maintainer has to do, the faster they’ll be able to review code and the
faster your patches will be merged. If a maintainer points out issues in your patch, offer potential
solutions and indicate the ones you prefer. If a maintainer points out related changes that need to
be made to take your change all the way through to completion, offer to make those changes. At the
end of the day, unless you’re fixing a critical bug, &lt;em&gt;you&lt;/em&gt; probably need your patches merged more
than the maintainer does.&lt;/p&gt;&lt;h1&gt;Becoming A Maintainer&lt;/h1&gt;&lt;p&gt;Becoming a maintainer of an existing codebase involves understanding it to the point where you can
confidentially review new patches and understand how they fit into the system as a whole.&lt;/p&gt;&lt;p&gt;There are three paths:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Rewriting/refactoring the codebase.&lt;/li&gt;&lt;li&gt;Reviewing code. This option forces you to read and understand the codebase.&lt;/li&gt;&lt;li&gt;Debugging issues, answering questions, and triaging bugs. Even more so than reviewing, debugging
requires you to read and understand a codebase.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Unfortunately, the first option is only usually available when you’re the &lt;em&gt;only&lt;/em&gt; maintainer so the
only remaining choices are the second two.&lt;/p&gt;&lt;h2&gt;Reviewing Code&lt;/h2&gt;&lt;p&gt;Reviewing code is &lt;em&gt;time consuming&lt;/em&gt; and it never gets easier; you just get better at it. Don’t expect
to wake up one day and suddenly get a speed-code-review superpower.&lt;/p&gt;&lt;p&gt;Treat a code review like you would async pair programming. You should understand the code you’re
reviewing &lt;em&gt;better&lt;/em&gt; (literally, I’m not exaggerating just to make a point) than the author. You
should be executing the code in your head. For parallel code, you’ll need to execute multiple
parallel copies at the same time.&lt;/p&gt;&lt;p&gt;If you’ve been asked to review some code and you find yourself thinking “I don’t think I’m qualified
to review this code”, that’s fine:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Review it as best you can anyways.&lt;/li&gt;&lt;li&gt;Ask someone to review the code as well.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;If you do this enough, you’ll eventually be able to review patches on your own.&lt;/p&gt;&lt;h2&gt;Debugging&lt;/h2&gt;&lt;p&gt;Debugging is a skill and, the more you work at it, the better you’ll get, independent of the
codebase. As an added benefit, the better you are at debugging, the better you’ll be at spotting
&lt;em&gt;potential&lt;/em&gt; bugs in your code as you write it. You’ll learn to recognize code “smells” and avoid
them.&lt;/p&gt;&lt;p&gt;You will &lt;em&gt;not&lt;/em&gt; magically get better at debugging by adding features or by fixing known/solved bugs.
Once you’re practiced at debugging, being familiar with a codebase will help you debug faster
however, the best way to get better at debugging itself is to debug &lt;em&gt;unfamiliar&lt;/em&gt; systems with
minimal information.&lt;/p&gt;&lt;p&gt;The key takeaway here is: debugging, especially debugging unfamiliar systems with little
information, is a skill you need to invest time in. If you hand off hard issues to “someone who
knows better”, you’ll never get better at it.&lt;/p&gt;&lt;h1&gt;Being A Maintainer&lt;/h1&gt;&lt;p&gt;Maintaining a large OSS codebase with many users is a rewarding experience. You often get to work
directly with users to address their problems and with contributes to merge their patches. You make
things happen.&lt;/p&gt;&lt;p&gt;However, maintaining an OSS project with a sufficiently large user-base will be frustrating at
times.&lt;/p&gt;&lt;h2&gt;Saying No&lt;/h2&gt;&lt;p&gt;As a maintainer, your job is to have a high-level view of the project, figure out how all the pieces
will fit together, and, ultimately, to say yes or no.&lt;/p&gt;&lt;p&gt;You will say no to many well-intentioned patches:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Good patches that implement useful features that are out of scope for the project.&lt;/li&gt;&lt;li&gt;Patches that require more work than they’re worth to bring into a mergeable state.&lt;/li&gt;&lt;li&gt;Correct but convoluted patches that you just don’t have time to review and/or simplify.&lt;/li&gt;&lt;li&gt;Features that aren’t worth the maintenance burden. There are many good features that aren’t
general-purpose enough to be worth maintaining.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;You will not have time to fix everything:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;You will not have time to fix every bug.&lt;/li&gt;&lt;li&gt;You will not have time to implement every feature, no matter how awesome.&lt;/li&gt;&lt;li&gt;You will not have time to patiently answer every user’s question.&lt;/li&gt;&lt;li&gt;You will not have time to shepherd every patch.&lt;/li&gt;&lt;li&gt;Users and contributors are &lt;em&gt;distracting&lt;/em&gt;. The more you have, the less time you’ll have to actually
fix their issues.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Safety Tips&lt;/h2&gt;&lt;p&gt;Maintaining OSS software is draining.&lt;/p&gt;&lt;p&gt;In most cases, front-line tech support and actual development work are isolated: those working
front-line support aren’t ultimately responsible for fixing bugs and those fixing bugs aren’t
directly in contact with customers. This means tech support doesn’t feel personally responsible for
customer issues and allows developers to focus on their work.&lt;/p&gt;&lt;p&gt;As an OSS maintainer, you won’t have that luxury. Your users will come directly to you directly to
&lt;a href=&quot;https://github.com/gorhill/uBlock/issues/38#issuecomment-91871802&quot;&gt;complain&lt;/a&gt; about code for which
you’re directly responsible.&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Start training a replacement immediately and treat every new contributor as a potential
maintainer. Even if you don’t completely burn out, you’ll need to hand off subsystems as the
project grows.&lt;/li&gt;&lt;li&gt;Users don’t file bugs when something works. You will always hear complaints, you’ll have to
listen hard to hear happy users.&lt;/li&gt;&lt;li&gt;You will feel like your users aren’t listening and are making the same mistakes over-and-over.
That’s because every user is a different user, every contributor is a different contributor. The
only solution is documentation or the patience of a kindergarten teacher. If you have to repeat
yourself, it should be documented.&lt;/li&gt;&lt;li&gt;Clearly say no, maybe later (“postponed”), or maybe if someone else takes the time to implement
this (“help wanted”). Saying no is always better than uncertainty and you won’t have time to
address everything.&lt;/li&gt;&lt;li&gt;Create processes and follow them. They allow you to draw boundaries and say no while keeping the
discussion impersonal. This is why governments use processes and couldn’t work without them.&lt;/li&gt;&lt;li&gt;Don’t take it personally or constantly second guess yourself. Just do the best you can then move
on when you can’t take it any more.&lt;/li&gt;&lt;li&gt;Remember that there is only one of you. You can (and should) say “I don’t have time for that”
and/or “that is not a priority right now”.&lt;/li&gt;&lt;/ol&gt;</content></entry><entry><id>https://stebalien.com/blog/fix-for-lenovo-x1-carbon-not-charging/</id><title>Fix For Lenovo X1 Carbon Not Charging</title><link href="https://stebalien.com/blog/fix-for-lenovo-x1-carbon-not-charging/" rel="alternate"/><updated>2018-05-07T00:00:00+00:00</updated><published>2018-05-07T00:00:00+00:00</published><content type="html">&lt;p&gt;I recently made the mistake of plugging my Lenovo X1 Carbon (Gen 5) into a 5 volt USB-C charger overnight. The LED on the side indicated that it was charging but, when I woke up in the morning, it obviously hadn’t. Worse, it now refused to charge even when plugged into the correct charger.&lt;/p&gt;&lt;p&gt;The fix is simple (although undocumented as far as I can tell). Basically, you need to reset the battery as follows:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Unplug from any power sources (this won’t work if you don’t do this).&lt;/li&gt;&lt;li&gt;Reboot into the BIOS setup (F1 on boot).&lt;/li&gt;&lt;li&gt;Navigate to the Power menu.&lt;/li&gt;&lt;li&gt;Select the “Disable built-in battery” option.&lt;/li&gt;&lt;li&gt;Wait for the laptop to power off and then wait 30 seconds.&lt;/li&gt;&lt;li&gt;Connect the power and start the laptop.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This will temporarily disable the battery which seems to reset any “bad charger” bits.&lt;/p&gt;&lt;p&gt;Hopefully, this will save others some time and frustration.&lt;/p&gt;&lt;hr&gt;&lt;p&gt;The X1 Carbon is otherwise a great laptop with an awesome keyboard. However, because this is &lt;em&gt;my&lt;/em&gt; blog and I can rant all I want here:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;The dedicated Ethernet port is pretty much useless given the ubiquity of USB-C. I’d have much preferred an additional USB or USB-C port.&lt;/li&gt;&lt;li&gt;The nipple seems a bit firmer than the one on my X220 and also seems to get into the “wandering cursor” state a bit more frequently.&lt;/li&gt;&lt;/ol&gt;</content></entry><entry><id>https://stebalien.com/blog/dracl-thesis/</id><title>DRACL (thesis)</title><link href="https://stebalien.com/blog/dracl-thesis/" rel="alternate"/><updated>2017-11-10T00:00:00+00:00</updated><published>2017-11-10T00:00:00+00:00</published><content type="html">&lt;p&gt;So, I never actually posted this and I figure someone out there may be interested… For my masters thesis, I designed (but never got a chance to implement) a decentralized (well, federated), privacy preserving, access control protocol. The purpose of this post is &lt;em&gt;not&lt;/em&gt; to explain DRACL but to get you interested enough to download my thesis and take a quick &lt;a href=&quot;https://stebalien.com/blog/dracl-thesis/static/thesis.pdf&quot; id=&quot;thesis&quot;&gt;look&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The primary contributions are:&lt;/p&gt;&lt;p&gt;First, an exploration of the privacy, security, usability, reliability, and performance trade offs involved in designing an such an access control protocol. We explore topics like,&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Account recovery versus security: no completely trusted third parties.&lt;/li&gt;&lt;li&gt;Privacy versus efficiency: e.g., choosing to not update something reveals that it hasn’t changed.&lt;/li&gt;&lt;li&gt;Security versus efficiency: cached credentials, etc.&lt;/li&gt;&lt;li&gt;Ease of integration: no new services, no server-side network requests, no account management, etc.&lt;/li&gt;&lt;li&gt;Ease of use: groups, no surprises, etc.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;Second, an interesting (not-yet-peer-reviewed-but-probably-mostly-correct-and-definitely-interesting) zero-knowledge (ish) set intersection protocol. We use this protocol to construct “keys” and “ACLs” such that the keys opaquely encode the set groups in which the user is a member and the ACLs opaquely encode the set of groups that have access to a particular piece of content. By opaquely, I mean that neither party learns anything about the sets that their keys encode (except what they learn through running the protocol, see below).&lt;/p&gt;&lt;p&gt;Given a key that intersects with an ACL, a user can prove that the key intersects:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Without either party learning anything other than the cardinality of the intersection (the user/prover learns this).&lt;/li&gt;&lt;li&gt;Without revealing any user/prover-identifying information to the website/challenger &lt;em&gt;other&lt;/em&gt; than the fact that the user’s key intersects with the ACL being proven against.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Furthermore, we can expire keys after a period of time (and even allow semi-trusted third parties to “renew” these keys without granting these parties access to the protected content).&lt;/p&gt;&lt;p&gt;There are quite a few interesting features/guarantees that are hard to sum up succinctly so I recommend you try skimming the thesis. It goes through a lot of trouble to try to explain DRACL in an approachable manner.&lt;/p&gt;&lt;p&gt;The primary downsides are:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Complexity. We designed the system to be simple from a usability standpoint but, under the covers, the actual logic, protocol, and crypto is complex.&lt;/li&gt;&lt;li&gt;Size limitations. No user in this system can feasibly have more than 1000 or so friends/groups of friends at a time.&lt;/li&gt;&lt;li&gt;Expensive crypto. Not “expensive crypto” from a cryptographers standpoint but expensive crypto from a systems engineers standpoint. Authenticating can take multiple seconds of CPU time.&lt;/li&gt;&lt;/ol&gt;</content></entry><entry><id>https://stebalien.com/blog/stash/</id><title>Stash</title><link href="https://stebalien.com/blog/stash/" rel="alternate"/><updated>2016-09-13T00:00:00+00:00</updated><published>2016-09-13T00:00:00+00:00</published><content type="html">&lt;p&gt;Stash is a library for efficiently storing maps of keys to values when one doesn’t care what the keys are but wants blazing fast &lt;code&gt;O(1)&lt;/code&gt; insertions, deletions, and lookups.&lt;/p&gt;&lt;p&gt;Use cases include file descriptor tables, session tables, or MIO context tables.&lt;/p&gt;&lt;p&gt;See the &lt;a href=&quot;https://stebalien.com/projects/stash/&quot;&gt;project page&lt;/a&gt; for more.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/disable-compulsory-addon-signature-verification-in-firefox-48/</id><title>Disabling Firefox Addon Signature Verification</title><link href="https://stebalien.com/blog/disable-compulsory-addon-signature-verification-in-firefox-48/" rel="alternate"/><updated>2016-08-07T00:00:00+00:00</updated><published>2016-08-07T00:00:00+00:00</published><content type="html">&lt;p&gt;As of Firefox 48, it’s impossible to disable mandatory addon signature verification without monkey patching Firefox or recompiling with &lt;code&gt;MOZ_REQUIRE_SIGNING&lt;/code&gt; unset. Personally, I find this unacceptable as the user should &lt;em&gt;always&lt;/em&gt; be in charge. It’s also completely useless as any party powerful enough to disable the signature verification in &lt;code&gt;about:config&lt;/code&gt; could just as easily sideload a powerful (but signed) extension like greasemonkey and then install a malicious greasemonkey script.&lt;/p&gt;&lt;p&gt;Rants aside, the correct solution (for the user) is to either recompile Firefox with mandatory signature verification disabled or use the Firefox Developer build. Unfortunately, Firefox is a monster and recompiling it just isn’t a viable option for me (or anyone with a laptop). Also unfortunately, prepackaged Firefox binaries are missing some &lt;em&gt;useful&lt;/em&gt; security features like PIE and dynamic libraries. Finally, the Firefox Developer build is a bit too bleeding edge for my taste (I would like my primary browser to be relatively bug free).&lt;/p&gt;&lt;p&gt;So, I’ve written a (disgusting) script that monkey patches Firefox’s &lt;code&gt;omni.ja&lt;/code&gt; to make signature verification optional again. I’ve only tested it on Arch Linux but it should work on all unix-like systems. However, if your &lt;code&gt;omni.ja&lt;/code&gt; file is not in &lt;code&gt;/usr/lib/firefox/&lt;/code&gt;, you’ll have to tell the script where to find it (i.e., &lt;code&gt;./nosign.sh /path/to/omni.ja&lt;/code&gt;).&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Requirements: coreutils, gawk, python2, sudo&lt;/li&gt;&lt;li&gt;Download: &lt;a href=&quot;https://stebalien.com/blog/disable-compulsory-addon-signature-verification-in-firefox-48/static/nosign.sh&quot;&gt;nosign.sh&lt;/a&gt; (&lt;a href=&quot;https://stebalien.com/blog/disable-compulsory-addon-signature-verification-in-firefox-48/static/nosign.sh.sig&quot;&gt;signature&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This script does &lt;em&gt;not&lt;/em&gt; disable addon signature verification, only makes it optional. To turn it off, you still need to set &lt;code&gt;xpinstall.signatures.required&lt;/code&gt; to false in &lt;code&gt;about:config&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; This script updates the &lt;code&gt;omni.ja&lt;/code&gt; file IN PLACE (using sudo).&lt;/p&gt;&lt;p&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; Use at your own risk.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/localtime/</id><title>Localtime</title><link href="https://stebalien.com/blog/localtime/" rel="alternate"/><updated>2016-06-16T00:00:00+00:00</updated><published>2016-06-16T00:00:00+00:00</published><content type="html">&lt;p&gt;Localtime is a small, light-weight go daemon for keeping the timezone up-to-date. It uses geoclue2 and systemd-timedated to do all the heavy lifting so it’s able to run with minimal privileges. See the &lt;a href=&quot;https://stebalien.com/projects/localtime/&quot;&gt;project page&lt;/a&gt; for more information.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/epipe/</id><title>Pipe to Emacs</title><link href="https://stebalien.com/blog/epipe/" rel="alternate"/><updated>2016-04-09T00:00:00+00:00</updated><published>2016-04-09T00:00:00+00:00</published><content type="html">&lt;p&gt;While there are many ways to pipe to emacs, they all involve either shuttling text by repeatedly calling emacsclient or writing to a temporary file. However, neither are necessary.&lt;/p&gt;&lt;p&gt;Basically, while emacs can’t (yet) read from a named pipe (FIFO), it &lt;em&gt;can&lt;/em&gt; read standard output from a process so, one gratuitous use of &lt;code&gt;cat&lt;/code&gt; later…&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-elisp&quot;&gt;(defun pager-read-pipe (fname)
  (let ((buf (generate-new-buffer &amp;quot;*pager*&amp;quot;))
        (pname (concat &amp;quot;pager-&amp;quot; fname)))
    (with-current-buffer buf (read-only-mode))
    (switch-to-buffer buf)

    (let ((proc (start-process pname buf &amp;quot;/usr/bin/cat&amp;quot; fname)))
      (set-process-sentinel proc (lambda (proc e) ()))
      (set-process-filter proc (lambda (proc string)
                                 (when (buffer-live-p (process-buffer proc))
                                   (with-current-buffer (process-buffer proc)
                                     (save-excursion
                                       ;; Insert the text, advancing the process marker.
                                       (let ((inhibit-read-only t))
                                         (goto-char (process-mark proc))
                                         (insert string)
                                         (set-auto-mode)
                                         (set-marker (process-mark proc) (point))))))))
      proc)))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;…and you can read a from named pipe. As an added bonus, this function will try to autodetect the correct mode.&lt;/p&gt;&lt;p&gt;To actually use this, I recommend the following shell script:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-sh&quot;&gt;#!/bin/bash
set -e

cleanup() {
    trap - TERM INT EXIT
    if [[ -O &amp;quot;$FIFO&amp;quot; ]]; then
        rm -f &amp;quot;$FIFO&amp;quot; || :
    fi
    if [[ -O &amp;quot;$DIR&amp;quot; ]]; then
        rmdir &amp;quot;$DIR&amp;quot; || :
    fi
}
trap &amp;quot;cleanup&amp;quot; TERM INT EXIT

SOCKET=&amp;quot;${XDG_RUNTIME_DIR:-/run/user/$UID}/emacs/server&amp;quot;

# Create a named pipe in /dev/shm
DIR=$(mktemp -d &amp;quot;/dev/shm/epipe-$$.XXXXXXXXXX&amp;quot;)
FIFO=&amp;quot;$DIR/fifo&amp;quot;
mkfifo -m 0600 &amp;quot;$DIR/fifo&amp;quot;

# Ask emacs to read from the names socket.
emacsclient -s &amp;quot;$SOCKET&amp;quot; -n --eval &amp;quot;(pager-read-pipe \&amp;quot;$FIFO\&amp;quot;)&amp;quot; &amp;gt;/dev/null &amp;lt;&amp;amp;-

exec 1&amp;gt;&amp;quot;$FIFO&amp;quot;
cleanup # Cleanup early. Nobody needs the paths now...
cat
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You will probably need to set the &lt;code&gt;SOCKET&lt;/code&gt; variable to your emacs socket filename.&lt;/p&gt;&lt;p&gt;Usage:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-sh&quot;&gt;dmesg --follow | epipe
&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><id>https://stebalien.com/blog/map-range-rust-macro/</id><title>Map Range (Rust) Macro</title><link href="https://stebalien.com/blog/map-range-rust-macro/" rel="alternate"/><updated>2015-07-13T00:00:00+00:00</updated><published>2015-07-13T00:00:00+00:00</published><content type="html">&lt;p&gt;Below is a (rust) macro for applying another macro to a range of values &lt;code&gt;0..N&lt;/code&gt; where &lt;code&gt;N&lt;/code&gt; is specified as a sequence of big endian octal digits. This is primarily useful for implementing traits for fixed-sized arrays.&lt;/p&gt;&lt;p&gt;This macro may eventually end up in some crate but I haven’t found a good place to put it (and it doesn’t really deserve its own crate).&lt;/p&gt;&lt;h2&gt;The Macro&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;lang-rust&quot;&gt;macro_rules! map_range {
    /* The actual macro */
    (%# apply $tmpl:ident, $shift:expr, $offset:expr, $by:expr, (0 $($rest:tt)*)) =&amp;gt; {
        map_range!(%# apply $tmpl, $shift, $offset, $by*2, ($($rest)*));
        map_range!(%# apply $tmpl, $shift, $offset + $by, $by*2, ($($rest)*));
    };
    (%# apply $tmpl:ident, $shift:expr, $offset:expr, $by:expr, (1 $($rest:tt)*)) =&amp;gt; {
        map_range!(%# apply $tmpl, $shift-$by, $offset, $by, (0 $($rest)*));
        $tmpl!((($offset) - ($shift)));
    };
    (%# apply $tmpl:ident, $shift:expr, $offset:expr, $by:expr, ()) =&amp;gt; { };

    /* Convert from little endien octal to big endian binary */
    (%# convert $tmpl:ident, (0 $($octal:tt)*), ($($binary:tt)*)) =&amp;gt; {
        map_range!(%# convert $tmpl, ($($octal)*), (0 0 0 $($binary)*));
    };
    (%# convert $tmpl:ident, (1 $($octal:tt)*), ($($binary:tt)*)) =&amp;gt; {
        map_range!(%# convert $tmpl, ($($octal)*), (1 0 0 $($binary)*));
    };
    (%# convert $tmpl:ident, (2 $($octal:tt)*), ($($binary:tt)*)) =&amp;gt; {
        map_range!(%# convert $tmpl, ($($octal)*), (0 1 0 $($binary)*));
    };
    (%# convert $tmpl:ident, (3 $($octal:tt)*), ($($binary:tt)*)) =&amp;gt; {
        map_range!(%# convert $tmpl, ($($octal)*), (1 1 0 $($binary)*));
    };
    (%# convert $tmpl:ident, (4 $($octal:tt)*), ($($binary:tt)*)) =&amp;gt; {
        map_range!(%# convert $tmpl, ($($octal)*), (0 0 1 $($binary)*));
    };
    (%# convert $tmpl:ident, (5 $($octal:tt)*), ($($binary:tt)*)) =&amp;gt; {
        map_range!(%# convert $tmpl, ($($octal)*), (1 0 1 $($binary)*));
    };
    (%# convert $tmpl:ident, (6 $($octal:tt)*), ($($binary:tt)*)) =&amp;gt; {
        map_range!(%# convert $tmpl, ($($octal)*), (0 1 1 $($binary)*));
    };
    (%# convert $tmpl:ident, (7 $($octal:tt)*), ($($binary:tt)*)) =&amp;gt; {
        map_range!(%# convert $tmpl, ($($octal)*), (1 1 1 $($binary)*));
    };
    (%# convert $tmpl:ident, (), $binary:tt) =&amp;gt; {
        map_range!(%# apply $tmpl, 0, 0, 1, $binary);
    };

    /* Public API */
    ($tmpl:ident @ $($num:tt)*) =&amp;gt; {
        map_range!(%# convert $tmpl, ($($num)*), ());
    };
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Example&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;lang-rust&quot;&gt;use std::mem;

trait AsArray&amp;lt;T&amp;gt; {
    fn as_array(&amp;amp;self) -&amp;gt; &amp;amp;T;
}

macro_rules! array_impl {
    ($value:expr) =&amp;gt; {
        impl&amp;lt;T&amp;gt; AsArray&amp;lt;[T; $value]&amp;gt; for [T] {
            fn as_array(&amp;amp;self) -&amp;gt; &amp;amp;[T; $value] {
                const LEN: usize = $value;

                if self.len() == LEN {
                    unsafe { mem::transmute(self.as_ptr()) }
                } else {
                    panic!();
                }
            }
        }
    };
}

// Call array_impl!(i) for i in 0..256. &amp;quot;2 0 0&amp;quot; means 0o200 (octal 200).
map_range!(array_impl @ 2 0 0);
&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><id>https://stebalien.com/blog/gazetta/</id><title>Gazetta</title><link href="https://stebalien.com/blog/gazetta/" rel="alternate"/><updated>2015-07-13T00:00:00+00:00</updated><published>2015-07-13T00:00:00+00:00</published><content type="html">&lt;p&gt;Gazetta is a static site generator written in rust (currently powering this website). You can find more details on the &lt;a href=&quot;https://stebalien.com/projects/gazetta&quot;&gt;project&lt;/a&gt; page.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/platter/</id><title>Platter</title><link href="https://stebalien.com/blog/platter/" rel="alternate"/><updated>2014-01-31T00:00:00+00:00</updated><published>2014-01-31T00:00:00+00:00</published><content type="html">&lt;p&gt;Announcing &lt;a href=&quot;https://stebalien.com/projects/platter/&quot;&gt;platter&lt;/a&gt;, a file server for direct file transfers. See the project page for details.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://stebalien.com/projects/platter/static/screenshot.png&quot; alt=&quot;Screenshot&quot;&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/january-2014-screenshots/</id><title>January 2014 Screenshots</title><link href="https://stebalien.com/blog/january-2014-screenshots/" rel="alternate"/><updated>2014-01-31T00:00:00+00:00</updated><published>2014-01-31T00:00:00+00:00</published><content type="html">&lt;p&gt;Yet another screenshot. This time with &lt;a href=&quot;https://github.com/baskerville/bspwm&quot;&gt;bspwm&lt;/a&gt; and &lt;a href=&quot;http://github.com/LemonBoy/bar&quot;&gt;bar&lt;/a&gt; (rendered with &lt;a href=&quot;https://github.com/overkill&quot;&gt;overkill&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/january-2014-screenshots/static/screenshot1.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/january-2014-screenshots/static/screenshot1.th.png&quot; alt=&quot;Screenshot&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/overkill/</id><title>Overkill Initial Release</title><link href="https://stebalien.com/blog/overkill/" rel="alternate"/><updated>2013-11-09T00:00:00+00:00</updated><published>2013-11-09T00:00:00+00:00</published><content type="html">&lt;p&gt;I have been working on a project I call overkill for the past few months or so. It’s is a publish-subscribe framework (or a functional-reactive programming framework if you want to use the latest buzz word) for collecting and distributing information on a local machine. Personally, I use it to generate my status bar (hence the name, overkill). See the &lt;a href=&quot;https://stebalien.com/projects/overkill&quot; id=&quot;project&quot;&gt;project&lt;/a&gt; page for more information.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/may-2013-screenshots/</id><title>May 2013 Screenshots</title><link href="https://stebalien.com/blog/may-2013-screenshots/" rel="alternate"/><updated>2013-06-02T00:00:00+00:00</updated><published>2013-06-02T00:00:00+00:00</published><content type="html">&lt;p&gt;New blog, new month, new screenshots.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/may-2013-screenshots/static/screenshot1.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/may-2013-screenshots/static/screenshot1.th.png&quot; alt=&quot;Screenshot 1&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;https://stebalien.com/blog/may-2013-screenshots/static/screenshot2.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/may-2013-screenshots/static/screenshot2.th.png&quot; alt=&quot;Screenshot 2&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/new-gpg-key/</id><title>New GPG Key</title><link href="https://stebalien.com/blog/new-gpg-key/" rel="alternate"/><updated>2013-05-07T00:00:00+00:00</updated><published>2013-05-07T00:00:00+00:00</published><content type="html">&lt;p&gt;It’s been a while but I figured I should probably make a new GPG key with
increase security (1024 to 4096 bits) and expiring subkeys.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-text&quot;&gt;-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

As of 'Tue, 07 May 2013 00:50:01 -0400', I have transitioned from my old key
to a new one in order to increase the security and sign with expiering
subkeys instead of my secure master key.

My old key fingerprint was:

    8F12 8D02 2B69 004E 8CF3  1890 D854 9F57 252B A385

My new key fingerprint is:

    327B 20CE 21EA 68CF A774  8675 7C92 3221 5899 410C

And the full new key:

    -----BEGIN PGP PUBLIC KEY BLOCK-----
    Version: GnuPG v2.0.19 (GNU/Linux)

    mQINBFGIaNIBEADbgEpvt4xVYSmvsIjcsmij7ds1Mt1Xe8dClPJ8iZu+E/MZYq/r
    Fnu5FJ1Hz09fGNwnwEhP06LfQ2cNWQiabiTqa9jqqu9/ulb1ElZvEhM9G5NedHR4
    9iIP5A75OzbywXIqYGBB6tbHLOBjLoIgSe8vJeFe/iTVy+52N0I+eiMCoq5Fcpql
    Liip/sHUYJx/wcnD7SoV8KbbfM2Oqcyan7AEAlHNeV4OXyKNNtufO0ZGaem+7vn8
    dl0Gv6jJkXAKGtL5V8eEljx6KOBO5ehvnu2KMvBHjJxPvW3gtby7m53yIqS74SFZ
    SK0wggMrCl0Lnwf3+YAYL2ycYxDcfcPjtQe8+X6kZcYKdnhAKvlhrFAIAaNXvM1I
    U6Oecw74jaRFlVkz3vlQ9FZLB0Am11OZ81EuihDFyMvB+lGYzCvrXbIPlvXC9uwT
    wUAcs4y47AzvUwMrvRhF7CFMAONWD60+eC9mzRc2/PGVPI5LsFpjgv2zlfZ8V9JQ
    T3/iST6FSwkmKyntW85ChTjpIGNgfBQzvRCD+8+he3TjvHaG5NN0TfjoBdZ4esYw
    b6r0AJgfP/EveXqojEz1UMWesf2WuYf/l9G5HstWJtrqB/EAcurX5WVNcIqE3QfF
    qVf2Rh/4/lO9oPC5g2M7k30/OSOl8uWNbXExxZyC18JDA1C5qn9wEEbt8wARAQAB
    tCNTdGV2ZW4gQWxsZW4gPHN0ZXZlbkBzdGViYWxpZW4uY29tPokCPAQTAQIAJgIb
    AwcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheAAhkBBQJRiGxQAAoJEHySMiFYmUEM
    gr8QAMO2Jc0F4mSqef2OxDzo2tcRcQ4vC+h08Nxljgp9mcpnZLvWdEhff0b9dyEo
    bBGlaISzcteGGRhJ1OyiLHPbjLnfCLCeZvQ7hCff/1YjVy170zDku4FIVBiwcbv4
    wMvdVVJX3iXC92R0G5tPuPJ5rw+41PRNLA3h9XT+K6Ns0PJsq4We1bNLeq0XSl7/
    chgID0g/AqjcxPT/8gZlLi3TytUi9MTN2XZUbTFPgChuMuKAZigeoPClCyu4p2t5
    IMp6qVGcnuNIww1+QUCi8pGdnsT/isU2gLcC9EGLAovyKipdqbS7IkPAAs1u0UAu
    zRJKVGYbTZrBeXr4wPUMFbyTbcM5cjXwC6rbJcyWfbp3FSWHSbHI9wgwg+JY2bB1
    fqTVaiZpTxmjKC3X4Y39K++f6Nr5oP4bxCPUg3h4i6VChiRpM+NZok5rZZKsBxfb
    QK/pXVnC3BesEEQtyoa+1fwg0mWGsbTsVdqz5T8cHUUgfUJM74Q+6Z9Jql8e3FR4
    IuRkWxsmeq4J9GgfxiddfhHZ9gp7txGOBx/pPft46t/9PdBxhoMPIPrK1eWUZnVm
    x0YRCvv4YTS3CEkoRHHEdmDfn5cyM2NNNWcM36pasfg6WLglrlfCyZFG6IgJBntU
    ZiTS0OakT1TFenheR8a1VAqN1DoQxecsCOGkkvlTQPv623sbiEYEEBECAAYFAlGI
    bd4ACgkQ2FSfVyUro4UXeACcCy/odWhokz4bnZOYLhio9gq1UOwAn0w/mM6DjeNg
    HOfa4+a8XyvyQ626tCFTdGV2ZW4gQWxsZW4gKE1JVCkgPHN0ZWJAbWl0LmVkdT6J
    AjkEEwECACMCGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgAUCUYhsUAAKCRB8
    kjIhWJlBDBkjEAC0wDHuLaRkk+LZLcC9iaxQEVkvgmtI1z39+TFhUbT8MEzAiQAj
    NKqsCpNG9H9zs9ZuEawPt67Nu/aKNEXg6T4Hfb9s1IjqYZCMGeGGogGanRaIc4Xq
    Qvfdj6OP/Js7Mw7jAHM/ffdPSVp7qGRK0VtCWi1rU+T1NIn1HKGdteW3xWe6iLpQ
    X30h5RZVKfx8fU1ayXxc1i+UQpKpW2c8xpsbOsKM60iEnbYPD1Al66VwGRRN7xhN
    HOZmzvcS5x1gJ5UoUyfXucUlll8Lhy50blRmSnhS3TItsL6pQELxran4jWsd3aXx
    une9YFcsx2eENTmLnVxVam1OQBjyJP64sjpkldqUOQAAE5Nr2GpVYOhCkSi9ZIEh
    IA/NPLC1ZsfGa57zVFlBK3qpOIpiCQ9VHUuNyXcG/0UxQ9Qs58CwUvDNEQE2gDgw
    1N3XOeY3CoCrzJyROqFQl2rS29Oo/7jxJnieeUPYafu516QsuXh374z1MZ4Hvw19
    YApYtNtytyzc6G1hlAhr9ow6TNFEkZHbX972LLriXGI4k3tqHUcvPrzbV4BaYelI
    9T3KTbO9hzs81muzc0IRKNAQAoe5ikeybLIE+/3pzhK3MADbo7kWJpMh3auy1mm0
    yy+aYCLMzRZyk1e2ohT98gRi7X33j5+84PFmk7s6H0bxVXmbznfLSxYD/IhGBBAR
    AgAGBQJRiG3eAAoJENhUn1clK6OFApoAnAgc+GJmXWSKD8cgMyCcM5qWGtarAJwJ
    jcpxQxyz0A4+8G5i0r4SYP/KprQpU3RldmVuIEFsbGVuIChDU0FJTCkgPHN0ZWJA
    Y3NhaWwubWl0LmVkdT6JAjkEEwECACMCGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIe
    AQIXgAUCUYhsUAAKCRB8kjIhWJlBDLj9D/4uTRLRgjaBCagcrtgCY1NCG3DmfWWF
    0WVqqCK5FCrbiTANJI09HIohpa0ZqSU4fw42loVrOOgRIn7HS6dhv5FVVeQXSl70
    06jvq42CiJvJ1Q9PY79Qa87oeX9lC4QKYrBhSTZkC1Q7uXtkPd2fJCIrNurp5+BI
    7JJp7IwARCk5mLXeR1qi6M+aluGdF7v6lp4O9w/KhIYzfV7pl9HLZM9/81Umrphb
    /gF8w+ErwUtdxKg2tkt+DitlUcojrtKwxdJJIp2N46IuV3zz68x7Ud8oOfgCUJNp
    AODkH/6zlzHZ8fHqPx3FNNGh2BUHrp4MF7953GFJZDHkmCRHNXVh1YuU77EVWU8y
    udlcCLNKTofIHQiBwuNcLzV2Fd2f6sdChLT2Xhq/YKZX6N9Q/ae6Bq7Q1QoQ2r0u
    w7kM4LripWUH91CHQPzOFLPS+T2GUZ5hUcI1fEw2udijymUHA7pbYxeCJhKGsvBM
    oSfs6awM5DfZpmxj+3TRVQHp7XG+YqZDGTymuZ7V7rEs3U17n5M9ebMMF1/EObTw
    x2yfM75kEj2Kz3Oti09LxIu+s42inVZTaB08hNUBHPKHVSWwwebUDebBNGUQ6PI7
    oStwX7KDx/1+xrxOdh8M96rSrwyPbiUCE9zXh7Drn4vL9yZeSnJ3NI0D9cTK1xOM
    6ZGqyUss2YElsYhGBBARAgAGBQJRiG3eAAoJENhUn1clK6OFyzUAoJouyBzJCay8
    9i/LkZME456qByKlAJ9O6VB9tOTxIDS/oPsl7rHpgvAm6bQqU3RldmVuIEFsbGVu
    IChHTWFpbCkgPHN0ZWJhbGllbkBnbWFpbC5jb20+iQI5BBMBAgAjAhsDBwsJCAcD
    AgEGFQgCCQoLBBYCAwECHgECF4AFAlGIbFAACgkQfJIyIViZQQz9Cg//VkDQ33m+
    2JumNddaNCONZZNqAvtS5+Pwu5FGkkrj2IYn45lU9jog2hG2ZyKlEZf1/ZDfM+hP
    qtxha6Tgpx3VWytN0aKzKUhav7S/58fhw46gmQvimIFBWS7RcMZskMl234hErvhd
    3LyjL6uJx93roJSlwCeARggn8LQbQgBcxcAAHh2Wlb74p/Le3CA/xYbkacyoCex2
    QuDF7lMCYTD1qDGkHiniiUDHXZYSNkfyT2XOkORZv3JoU0eNhO2RRP3FI5hsztOm
    BVOfjvOvfWQDRJDczVvhraLoRenTsnI/nDI+T5kAQrSDJ0xc44DjRpxx5vu2sasY
    Zggd/9mbJkYC2hA7dRZRIP1/YZPFw8ekvTFR0FM7LkSvFh9GJhwnv/4xK+/bRv5r
    6SVOc47kH/gTfZSjusu4feWRIeaEvuwC90ORmLujM77bR0o0hkZekYVPdF+m6tEy
    0b4LxNt6dtiqEXewSEwnfFHcXLc+Y1M2bUspREo7yz5krplJgXhkeDLvLmTUURhN
    2Tgkc4ksB1NVaNRtDr6Ry5zIbYZB3AMcsNvV1mOeNYeqYmUTB7yaTEpNQsndQD9i
    YK9hA5vsaHLPJj7drLKfgHT7l7v0O90dVL6S7JoboEshaEgcEp+jXBl9crz5xLqx
    u124DrLKqsqmDyr83VIdMuPOTL32oG2dKwGIRgQQEQIABgUCUYht3gAKCRDYVJ9X
    JSujhZusAJ0Rdvv++yKLuwL7KNQGP6cNsu+3DACcDqEOP1VNT/ke4LpwdL6sHdoi
    Xaq5Ag0EUYho0gEQAMq8FLqp2zHs8ORlk5qDS5y8Fdi9RsttWEybLGMzx6Ka2A73
    SaWm0+oLtpLehuAAWF0iTAj9+TztzbmUWfPFr4Zj/i4L73XOeAgz2cCFHOEjjgTF
    Y804Hk99WVhQdZMikwyTPk2s6ccf6TTxHqdtut5EqTKEU1yWf2CU4Aq2uidWxnI6
    5TSE4HPCW4bHhZxZF1YX9HfR3oE3S7PXa3ojNQ+5pXyXTiw+nrcGnIlN0GOIZUYj
    z7R+HjczjuLngG3jFjHIXyplLbezZUxwXaseqHgrPwFZaAOXPwjkzbKRE/PaLU4l
    b8IbZWJ05Ho8lgsci0CILaMjclC0WVdLf+uXfo09oOwIUz34nQqoDSSvIW+gMe+P
    Skf6slSwdsQhi5idWLa17TbnvL6+jSr+MMPp3GvcK+Jo+AlqE/Ryx5aY1IybYCLn
    RXLKhc4GNYPZD+9UwClF5pPvl/PfIxnu7Eri6Iw2DLJ1zFJiM6+5xGjTN+pO8Q9x
    UXqb/StYq1SwehjtHpl1hVEsTX6YG35kv02zzd7NrKEnvia3fzBhy/2chh/VWHEk
    Zs3dhW4rOHa+Y+NO4CHUfNtbCFl9dreDb1Upvqav/OeMKSarPzHU2Xly9EeXDk/L
    +s5fRTsy6mR9M36gZclTqpWniTEk2PGm2LbJt47j4qup2FplE0WxSkGLR14rABEB
    AAGJAiUEGAECAA8CGwwFAlGIbGkFCQHhNxcACgkQfJIyIViZQQyWSg/+LWq9J7il
    JOPMdgHKOkVsYRpU+OnEevnmfqs+THFlJY/hpMPHDiOJ5pAnoIF3sUnEIN5CrcOz
    YXxMoszsFTiL22WWPaF1v2go9KFpFpe2YJrp6fFMs9W+rD2K1GfAtY3wlfRK9mhn
    n0IHO9Y2qY/D1/jgmz4735AwgNLu7z5G9QU1AA9/2CzgJioCnFv59FJuks5hac3E
    r1HhS15sAvNnLBHjIlXZsRSui2bCL2sYFLNcf2A1AW/wEE0RPEvoP/xMh6EhvM9u
    Biwi8n7EkbAFptp6fW9yGmmWytcsGHxwfhG0wBy4gMg6FNl5skf19NdtSrzn081N
    uLK3Av3GQAp7AaSUYnhQS7sO1/klRiBoQXN97W9wn+N+wCG9LfdZQnUVS28qW4Ar
    5BNjeN1PW3HI+bbi/ElsQTUlMpLoGhPUE9niOadJqY4TOZKVunLppuEjaPBWbFK9
    ZRFQWmm4pMGu19o2ywYVVVhhdM9wGfXFMxbIyZp8YfTcSIHmPuoTq9dqUregC0sk
    oxSHQw8eoLgOIrkOsvnRDiVZsjMKaKg4I8GhqOPK+vD/2/nDre/ytv5Q9+GI5+wL
    JhTQwIMYITWT3OsXkttowYpIqAqzPdGX+0EiTcod3IhEN9XdM9ZatYpBBbsS/A/Z
    xShFmpTNIo1z99ARqe8JQuUYQ6Y4KNblNwm5Ag0EUYhrzgEQAOvRfIF3bN6uRSuu
    jib06s5JKcPpBDmjkfZPwgr7A5G0hfk17umfDD4YyDEKiFmP6IJ4YBabbm0iI1FA
    erETYGKg9nK1cSYen/sIV1BQf38cTYKmBKHefTD/XB45umcLV7TryqGjmW/jbgKP
    D1TjBpnkz5brfrt8KGHoUcFRDkQ7Bm/ufwPxp+bRYu25+MIRHdigQdxvm4vbgIfy
    iG0ENs9e4B1cuk4GKtAr1J3U0q3NufOCnk/gTNNSPh1uaHCH0EZTUJBV+mwyqe4i
    2/ksQg2Ufpy3wz0Jczd58XLc4c0JYJ1ir0fBRobqMJWlYuiXjYzXj8y4se5IDojm
    +ljudRK1nveoOB+tHygLoMIiKOOuL+VmLmcOHqySzObFfRbpbvp7wKcJunCcVF9i
    J6aYfK0XG7eK15u83QaKw/jGIdTh9Z2Ic4gl1YdkpLzK33nlzk/bM2SIUDhK3vIP
    jctXe4bF8OZ+YIrc1XtlGhJ6c3bqSmOiEO7tVkr6vwmJQWnVfedxt9qJqTxwnE9J
    WQGpbijio3xommhFomTalpZ+NtuN3SYFMSSSiVkVRXmcbpxkaObHJvY1g50Fqh7e
    7/KN4lpyJ/k7J1DWuMaBX5jHT9PrsnbaXli8rw5ylivSghs92Lgqw/gfoIU8JiRd
    1iqxA/j/Ps+wuQwZLJcpOOU9zU65ABEBAAGJBEQEGAECAA8FAlGIa84CGwIFCQHh
    M4ACKQkQfJIyIViZQQzBXSAEGQECAAYFAlGIa84ACgkQmVTTNRXa64SChw//W/Gx
    crHkgHtFjns0clDCqmmakS2HRRaAJ53Q62ntr/9nKifPUATNqdtiv+vRCPeuz8oM
    WgWQYVb+hBi3hbEOXISdOT4PZFAjg66LvdMA9ocKFa8RbXYwL8ThR9uEh89REIEf
    aTdZgSJGLxiafTNJ2KPS3FJerJAFqtbPMojLb9Oui5659ySDloXC+Owd8a+D2F+V
    IbMrO6UOJ/JlajmDLkwsI3ypjiGgNIdhipSpOBY2jBsA8kOoqhLrWPFVpCxvH9Ba
    jJKaK3N81ytSQGqsReRTJAJATGGvcX53u416NByWDssxWyWteKK2xIAHhcl8g3X9
    ItBGuHcddrkTHlLDSFkYKglEPY6PQsCpBlHsBBjBkRYznFCw+PkbNDc4FkANCtGR
    WgBqfzE+0sjAy//xjbu4M1+lcYizYoyO8xI+cdy9TGmNIoevhHxEZiGlv99PXGkc
    1kSYbBBOABXHOLGlV0mOP6pK8Ve8AJk6qSGphmpGbDVwC1pl5Am61vPFXjAErGaJ
    lfFhq5s9iXtDecJVmTHaepDLL+fC5DwVsNL99oqf/coXIweSWbnOQzR5rabjis1p
    fOb/+qpaMRX5liBpUpMxBeIDmb41EOopuVeW37QD1bYp+q4/i0alDN/F0Ev6rqxX
    1A7ntUPq1Nmmfl/2kJmFapg+4QpAwIRgZfyrSFKn4Q/8CciaRYHAgLNJoc9cYgzB
    hLHSnM6kyF1UmcK9yb/SAjThPQcg4zeRDI4/WPsE4R8qZHCq4tH47kgTMPNRTak6
    b6KPYaKCgaGP81I8ndSlMcmvhROmQCVuWueWoMn1laoqups4nSW+vr6qH19bHqEc
    D7jQiUrTcRCVGEJS6tkH/PPwyBRmWTARY3Rd6Y/D/jSkJTOYT/e4DKHBrLyabXzy
    9v6E+e6eExJsPV3P5f8zngU/L/RFS3oqykj96WEN4Tx0OyyvGDx2MIxJKujNCbxj
    6+TMcmgLbm+PSP0zMUu3R07VUnuzE8nhi8GBx5LW7ONim70UUKFQp5p5jZq0lgkQ
    xB4q+kWI7speobt3osgmFDNEzWU8PZfl9smeFOxDF5ja/myC1KRhFecwtZQvPJ99
    PH43VIfl41MrC39lhA5DfeO+FLLUa1KDa1sXRLVdAtme6z1wVbYrc7/X5E69d+Po
    DyknudabLdW+LxcHXAIP6z1wpWflCVIRziaefilXvY5eq8h/jwU3AfdgygBSJty+
    wmhmuOHAWWaURwxuU77w32e2lHyXhNIm0aDPiYISOPo1x3yRYMtVDWzcfG6Gc6HM
    T7gbnzPXt2CrSm+Noineb4TFGQZLgtgRLut+PjEkSCHjc6RRGFy39VZChgJzkEFN
    MXJjnYZCKLva+UJATvt09dQ=
    =hRIT
    -----END PGP PUBLIC KEY BLOCK-----
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.19 (GNU/Linux)

iQIcBAEBAgAGBQJRiI01AAoJEHySMiFYmUEMZzIP/Asu/YJ6QloHkLso3rT4ihFP
suVLXWouks6IyiCLr96Erf/+50D36KXd07/aMD54rNrvIPiq7RNhcQasyH7zAQtq
bGJIQqZATLfp3EjVvHx3H4szHyA+pW+HU1uzh6HigAfQMGTpOsZsvf8SLmB8sohj
HzMyJdQCzDKXQLotGaSQ4n79Xr2K7lQATz10aOc0qGZBENHkFvYJ7MNEFv+LuWio
vVwduuStvBOYRts6z8pHv7M3ZgzOiqSx7vtRCXRwuRLAzisc09ogNRp9CXKWkO9F
bkiMKOhHvRWMpthPcgVxbA9151n8LNDL02hdSJ2LhJm10I6XKX1dhzqVtaVLXkbd
pLRvkyNg4PbFD5bd5TPmsDoVI3mW/CsNgDlCHDeMf8aS1fGxhBQVO6+X/YmiUc9o
lqSdOnt2Kf1gTWSFFjJ32GgpLD6ajAkvxqcDZTCXbdaqr83jBbAaZ4EiHIKruOqT
slIEI8KbY9LItMVZk55435H2MzDvazPOPrW0uRrWExnE5A4n6szcLrkEIaG99AS3
YVC+ycEHWNtaDyvyXb2mbJICtIs8y/Nhrz2WT52fDYFJLakVp04Zgvdbn732ISpg
AW+sJ6Sg/sHpX4V2SV290W4wDCJ6LBqxPzb0zs/2JvGR+J/ByIQpoD5W6XdaHJn5
tTcsGtiQ0e7BNC/poqLViEYEARECAAYFAlGIjTUACgkQ2FSfVyUro4W2wwCcCEEf
kUu9EqFYq0Vdrhqsd5bOIp0An237odqQt2MFZVNM7YtsH6cPwk2z
=/NJg
-----END PGP SIGNATURE-----
&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><id>https://stebalien.com/blog/my-configuration-files/</id><title>My Configuration Files</title><link href="https://stebalien.com/blog/my-configuration-files/" rel="alternate"/><updated>2010-08-22T00:00:00+00:00</updated><published>2010-08-22T00:00:00+00:00</published><content type="html">&lt;p&gt;I have finally got around to making a git repository for my configuration files.&lt;/p&gt;&lt;p&gt;Here they are: &lt;a href=&quot;http://github.com/Stebalien/dotfiles&quot;&gt;http://github.com/Stebalien/dotfiles&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/turn-zim-desktop-wiki-into-calendar/</id><title>Turn the Zim Desktop Wiki into a calendar</title><link href="https://stebalien.com/blog/turn-zim-desktop-wiki-into-calendar/" rel="alternate"/><updated>2010-05-18T00:00:00+00:00</updated><published>2010-05-18T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;Why&lt;/h2&gt;&lt;p&gt;I recently switched to Arch Linux and decided to ditch evolution (a good but bloated program). &lt;a href=&quot;http://www.claws-mail.org/&quot;&gt;Claws Mail&lt;/a&gt; works perfectly as an email manager but I couldn’t get its calendar plugin to work properly. I have been using &lt;a href=&quot;http://zim-wiki.org/&quot;&gt;Zim&lt;/a&gt; for a while and noticed that it had a very basic calendar plugin; this was exactly what I needed. The plugin allows users to create pages in their wikis for individual days: no complex forms to fill out, just a simple page to keep track of what you are doing on a given day.&lt;/p&gt;&lt;h2&gt;What&lt;/h2&gt;&lt;p&gt;As Zim lacks desktop integration so I wrote two python scripts for conky integration:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;&lt;p&gt;zim-conky_cal.py&lt;/p&gt;&lt;p&gt;Prints a calendar (like the cal command) with the current date and
appointments highlighted.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;zim-conky_events.py&lt;/p&gt;&lt;p&gt;Lists the next 5 events or all of the events in the current month
and the next, whichever comes first.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/turn-zim-desktop-wiki-into-calendar/static/screenshot1.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/turn-zim-desktop-wiki-into-calendar/static/screenshot1.th.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;I also wrote a program for adding events to the calendar (zim-cal.py and zim-cal.ui). Select some text, run the program and double click the date to add your text to calendar. You can also input your own text by clicking the edit button (the big button on the right).&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/turn-zim-desktop-wiki-into-calendar/static/screenshot2.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/turn-zim-desktop-wiki-into-calendar/static/screenshot2.th.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;How&lt;/h2&gt;&lt;p&gt;First: Enable the calendar plugin (Edit-&amp;gt;Preferences-&amp;gt;Plugins-&amp;gt;Calendar).&lt;/p&gt;&lt;p&gt;Download: &lt;a href=&quot;https://stebalien.com/blog/turn-zim-desktop-wiki-into-calendar/static/zimcal.tar.bz2&quot;&gt;zimcal.tar.bz2&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Conky scripts&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Edit CAL_PATH to point to the folder that stores your Zim calendar.&lt;/li&gt;&lt;li&gt;Add &lt;code&gt;${execpi 300 /path/to/zim-conky_cal.py}&lt;/code&gt; and &lt;code&gt;${execpi 300 /path/to/zim-conky_events.py}&lt;/code&gt; to your conkyrc&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Zim-Cal program&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Edit CAL_PATH to point to the folder that stores your Zim calendar.&lt;/li&gt;&lt;li&gt;Make PROG_PATH point to the directory where you put “zim-cal.ui”&lt;/li&gt;&lt;li&gt;If you intend to use this program regularly, you should probably assign a global hotkey to it in your window manager.&lt;/li&gt;&lt;/ul&gt;</content></entry><entry><id>https://stebalien.com/blog/make-gksu-and-policykit-red/</id><title>Make Gksu and Policykit red</title><link href="https://stebalien.com/blog/make-gksu-and-policykit-red/" rel="alternate"/><updated>2010-04-29T00:00:00+00:00</updated><published>2010-04-29T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I was bored one day and decided to make my gksu(do) and policykit dialogs red. The results are actually quite nice.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/make-gksu-and-policykit-red/static/gksu.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/make-gksu-and-policykit-red/static/gksu.th.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Add this to the bottom of your gtkrc file:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;style &amp;quot;gksu&amp;quot; {
    bg[NORMAL] = &amp;quot;#770000&amp;quot;
    bg[ACTIVE] = &amp;quot;#550000&amp;quot;
    bg[PRELIGHT] = &amp;quot;#990000&amp;quot;
    bg[SELECTED] = &amp;quot;#550000&amp;quot;
    bg[INSENSITIVE] = &amp;quot;#220000&amp;quot;
}

widget &amp;quot;GksuuiDialog*&amp;quot; style &amp;quot;gksu&amp;quot;widget &amp;quot;PolkitGnomeAuthenticationDialog*&amp;quot; style &amp;quot;gksu&amp;quot;
&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><id>https://stebalien.com/blog/packagekit-with-apturl/</id><title>Packagekit with apturl</title><link href="https://stebalien.com/blog/packagekit-with-apturl/" rel="alternate"/><updated>2010-04-28T00:00:00+00:00</updated><published>2010-04-28T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Although I no longer use packagekit, I still have my apturl script so I thought I would post it. This script will allow you to open apt:// scripts with packagekit. (Just save this to a file, mark it executable, and tell your browser to open apt scripts with it).&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;#!/bin/bash
/usr/bin/gpk-install-package-name $(echo $* | sed -e 's/apt:\/\?\/\?//')
&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><id>https://stebalien.com/blog/useful-bash-functions/</id><title>Useful Bash functions</title><link href="https://stebalien.com/blog/useful-bash-functions/" rel="alternate"/><updated>2010-04-27T00:00:00+00:00</updated><published>2010-04-27T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;cdd: cd and list the files.&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;function cdd(){ cd $*  ls --color}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;changelog: get the change log for a program&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;changelog() {
    log=/usr/share/doc/&amp;quot;$*&amp;quot;/changelog*
    if [ -r $log ]; then
        less $log
        unset log
    else
        log=/usr/share/doc/&amp;quot;$*&amp;quot;/CHANGELOG*
        if [ -r $log ]; then
            less $log
        fi
    fi
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;mkdircd: make a directory and move in.&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;function mkdircd() {
  mkdir $* cd ${!#}
}
&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><id>https://stebalien.com/blog/screenshot-of-arch/</id><title>Screenshot of Arch</title><link href="https://stebalien.com/blog/screenshot-of-arch/" rel="alternate"/><updated>2010-04-25T00:00:00+00:00</updated><published>2010-04-25T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I have been trying Arch Linux in VirtualBox and will probably switch when I get around to it (or at least duel boot along with Ubuntu).&lt;/p&gt;&lt;p&gt;Here is what it looks like so far; if you have any questions about tools, configs, etc., ask and I will post.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/screenshot-of-arch/static/screenshot.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/screenshot-of-arch/static/screenshot.th.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/kupfer-plugins/</id><title>Kupfer Plugins</title><link href="https://stebalien.com/blog/kupfer-plugins/" rel="alternate"/><updated>2010-04-25T00:00:00+00:00</updated><published>2010-04-25T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;After getting frustrated with GNOME-Do’s memory hogging, I switched to &lt;a href=&quot;http://engla.github.io/kupfer/&quot; id=&quot;kupfer&quot;&gt;Kupfer&lt;/a&gt;. Kupfer is a lightweight, extensible application launcher like Do (as it is now called) but much more powerful and easier to extend (it is written in python).&lt;/p&gt;&lt;h2&gt;My Plugins&lt;/h2&gt;&lt;p&gt;So far, I’ve written the following plugins:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;gwibber\_plugin.py&lt;/code&gt;: This plugin allows you to send messages from Kupfer through Gwibber (with no configuration).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;exaile\_plugin.py&lt;/code&gt;: This plugin allows you to pause, play, skip, and go backwards in Exaile. It is based on the Rhytmbox plugin.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;+1: &lt;code&gt;evolution\_plugin.py&lt;/code&gt;: I did not write this plugin (although I did do a fair bit of editing). The Evolution plugin adds an evolution contact source (and works with the built in email plugin).&lt;/p&gt;&lt;p&gt;Download: &lt;a href=&quot;https://stebalien.com/blog/kupfer-plugins/static/kupfer-plugins.tar.gz&quot;&gt;kupfer-plugins.tar.gz&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/start-conky-only-after-root-window/</id><title>Start conky only after the root window loads</title><link href="https://stebalien.com/blog/start-conky-only-after-root-window/" rel="alternate"/><updated>2010-01-02T00:00:00+00:00</updated><published>2010-01-02T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Every time I logged in, conky would start up before nautilus loaded the desktop. This caused conky to load as a floating window that stayed on top of all other windows. I have finally gotten around to fixing this
problem.&lt;/p&gt;&lt;p&gt;&lt;s&gt;&lt;a href=&quot;http://paste2.org/p/591516&quot;&gt;Here&lt;/a&gt;&lt;/s&gt; (gone, email if found) is a simple python script that waits for nautilus to load the desktop before starting conky.&lt;/p&gt;&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;I stored the script referenced in this post in a paste-bin and now it’s gone. Live and learn…&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;This script is probably very inefficient but it gets the job done.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/humanity-icon-for-caffeine/</id><title>Humanity Icon for Caffeine</title><link href="https://stebalien.com/blog/humanity-icon-for-caffeine/" rel="alternate"/><updated>2009-11-15T00:00:00+00:00</updated><published>2009-11-15T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;For those who don’t know, Caffeine is a small program for Linux that lets a user prevent his or her computer from entering a power save state. If a user wishes, he or she can even configure caffeine to automatically inhibit power-saving when watching a flash movie, or running VLC, Totem etc. For more information, visit its website &lt;a href=&quot;http://pragmattica.wordpress.com/2009/10/21/caffeine-1-0-released/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;As a user of both Caffeine and the new Humanity icon theme (the default icon theme in karmic), I made a very basic gray-scale version of the Caffeine icon. You can download it &lt;a href=&quot;http://gnome-look.org/content/show.php?content=115535&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/service-manager-for-karmic/</id><title>Service Manager for Karmic</title><link href="https://stebalien.com/blog/service-manager-for-karmic/" rel="alternate"/><updated>2009-11-01T00:00:00+00:00</updated><published>2009-11-01T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;&amp;gt; Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Annoyingly, as karmic has mostly switched to Upstart, it does not include a service manager. While I hope that the gnome service-manager will be updated to include support for upstart soon I have, for the interim, written a very simple service manager.&lt;/p&gt;&lt;p&gt;Be warned: When I say “very simple” I mean “very simple, noob unfriendly, and potentially dangerous”. While it should not harm your computer, I make no guarantees because it is my first PyGTK program and was written in my spare time over a couple of days. A word of warning, the code is very messy and inefficient (understatement).&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/Stebalien/ssm&quot;&gt;Link&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/new-desktop-theme/</id><title>New Desktop Theme</title><link href="https://stebalien.com/blog/new-desktop-theme/" rel="alternate"/><updated>2009-09-24T00:00:00+00:00</updated><published>2009-09-24T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Just in case anyone is interested, here is my new desktop layout.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/new-desktop-theme/static/screenshot.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/new-desktop-theme/static/screenshot.th.png&quot; alt=&quot;Screenshot&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;This theme works with 1280x800 screens.&lt;/strong&gt; (If you have a different size screen, you will have to modify it)&lt;/p&gt;&lt;p&gt;Download: &lt;a href=&quot;https://stebalien.com/blog/new-desktop-theme/static/theme.tar.gz&quot;&gt;theme.tar.gz&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Font:&lt;/strong&gt; &lt;a href=&quot;apt:ttf-droid&quot;&gt;Droid Sans&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Panel:&lt;/strong&gt; &lt;a href=&quot;http://code.google.com/p/tint2/&quot;&gt;tint2&lt;/a&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Install from &lt;a href=&quot;https://launchpad.net/%7Ekilleroid/+archive/ppa&quot;&gt;this&lt;/a&gt; PPA&lt;/li&gt;&lt;li&gt;The tint2rc file (from my theme package) to “~/.config/tint2/”&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Background:&lt;/strong&gt; custom (includes panel and conky backgrounds)&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Either use my background (background.png) or replace the background layer in background.xcf with your own image and save it as a png.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Conky:&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;System Information and Calendar.&lt;/p&gt;&lt;p&gt;You can find them in the theme package.&lt;/p&gt;&lt;p&gt;To install:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Create a new folder “~/.conky/”&lt;/li&gt;&lt;li&gt;Copy “cal.conkyrc”, “cal.py” and “system.conkyrc” to “~/.conky/”&lt;/li&gt;&lt;li&gt;Add a new startup item &lt;code&gt;sh -c &amp;quot;conky -d -c ~/.conky/system.conkyrc; conky -d -c ~/.conky/cal.conkyrc&amp;quot;&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Theme:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Metacity: Nooto&lt;/li&gt;&lt;li&gt;GTK2: Ghostship&lt;/li&gt;&lt;li&gt;Icons: Elementary&lt;/li&gt;&lt;/ul&gt;</content></entry><entry><id>https://stebalien.com/blog/write-file-as-root-from-non-root-vim/</id><title>Write a file as root from a non-root vim.</title><link href="https://stebalien.com/blog/write-file-as-root-from-non-root-vim/" rel="alternate"/><updated>2009-08-14T00:00:00+00:00</updated><published>2009-08-14T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;Problem&lt;/h2&gt;&lt;p&gt;I often edit config files in vim, try to save them, and then realize that I ran vim as a normal user and the config file is in /etc. In the past I would close vim and redo all of my changes as root (using sudo). This is a real pain especially if I had made a lot of changes.&lt;/p&gt;&lt;h2&gt;Solution&lt;/h2&gt;&lt;p&gt;Add the following to your ~/.vimrc:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-vim&quot;&gt;cmap w!! w !sudo tee % &amp;gt;/dev/null&amp;lt;CR&amp;gt;:e!&amp;lt;CR&amp;gt;&amp;lt;CR&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A while ago I came across this &lt;a href=&quot;https://raam.org/2009/saving-files-as-root-from-inside-vim/&quot;&gt;post&lt;/a&gt; that describes how to save a file as root from a non-root vim by adding &lt;code&gt;cmap w!! w !sudo tee % &amp;gt;/dev/null&lt;/code&gt; to my &lt;code&gt;.vimrc&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The problem is that, after running this command, vim will notice that the file has changed on disk and ask the user if he or she wants to reload it. After a while this got annoying, therefore the multiple CRs (enters) and the :e! (reloads the file).&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/apt-repository-permissions/</id><title>Apt Repository Permissions</title><link href="https://stebalien.com/blog/apt-repository-permissions/" rel="alternate"/><updated>2009-08-06T00:00:00+00:00</updated><published>2009-08-06T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I just posted a solution to &lt;a href=&quot;http://brainstorm.ubuntu.com/item/20622/&quot;&gt;this&lt;/a&gt; idea but thought that I should share it here.&lt;/p&gt;&lt;p&gt;Here is the problem: In order to get the latest features on Ubuntu, people are adding a lot of PPAs. For now there hasn’t, as far as I know, been a case in which a PPA owner has uploaded a malicious package but this is a possibility. Uploading an end user application, such as shutter, with malicious code would be problematic but not devastating. On the other hand, uploading a malicious sudo package would be devastating. Here is my solution.&lt;/p&gt;&lt;p&gt;Different repositories would “own” packages:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Ownership would be set in a file such as /etc/apt/ownership/.list&lt;/li&gt;&lt;li&gt;A special system packages file would be created that would designate system packages (sudo, pam etc…).&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Apt repositories would have permissions:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Ultimate Trust: Update and Install packages from this repository regardless of ownership including system packages.&lt;/li&gt;&lt;li&gt;All: Update and Install new packages from this repository regardless of ownership (except system owned packages).&lt;/li&gt;&lt;li&gt;Owned only: Update and install only owned packages.&lt;/li&gt;&lt;li&gt;No Updates: Install owned packages from this repository but do not download updates from it.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Flags:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Warning: There would be a warning flag that a user could set on a repository that would warn when packages are updated or installed from that repository.&lt;/li&gt;&lt;li&gt;System: There would be a system flag that could be set on security related packages (sudo, bash etc…) that would prevent all but “Ultimate Trust” repositories from installing/updating them.&lt;/li&gt;&lt;/ol&gt;</content></entry><entry><id>https://stebalien.com/blog/dark-minimal-lock-dialog/</id><title>Dark-Minimal Lock Dialog</title><link href="https://stebalien.com/blog/dark-minimal-lock-dialog/" rel="alternate"/><updated>2009-07-27T00:00:00+00:00</updated><published>2009-07-27T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I finally uploaded my first contribution to &lt;a href=&quot;http://gnome-look.org/&quot;&gt;gnome-look.org&lt;/a&gt;: &lt;a href=&quot;http://gnome-look.org/content/show.php?content=109151&quot;&gt;Dark-Minimal Lock Dialog&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;Screenshot&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/dark-minimal-lock-dialog/static/screenshot.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/dark-minimal-lock-dialog/static/screenshot.th.png&quot; alt=&quot;Screenshot&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;Download&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;http://gnome-look.org/content/show.php?content=109151&quot;&gt;Gnome-Look.org&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/navigate-text-with-vim-keys-on-linux/</id><title>Navigate text with vi(m) keys on linux</title><link href="https://stebalien.com/blog/navigate-text-with-vim-keys-on-linux/" rel="alternate"/><updated>2009-06-05T00:00:00+00:00</updated><published>2009-06-05T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;Intro&lt;/h2&gt;&lt;p&gt;Lifehacker recently &lt;a href=&quot;http://lifehacker.com/5277383/use-caps-lock-for-hand+friendly-text-navigation&quot; id=&quot;lh&quot;&gt;posted&lt;/a&gt; a AutoHotkey script for windows that allows text navigation using letters instead of the arrow keys. In response to &lt;a href=&quot;http://identi.ca/wersdaluv&quot; id=&quot;wersdaluv&quot;&gt;@wersdaluv&lt;/a&gt;’s post, I wrote a very simple script that allows users to navigate text using the standard vim keys (hjkl) when the caps-lock key is toggled. Feel free and add to my very basic script.&lt;/p&gt;&lt;h2&gt;Steps&lt;/h2&gt;&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;I stored the script referenced in this post in a paste-bin and now it’s gone. Live and learn…&lt;/p&gt;&lt;/blockquote&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Download my script (via copy and paste) from &lt;s&gt;&lt;a href=&quot;http://paste2.org/p/249823&quot;&gt;here&lt;/a&gt;&lt;/s&gt;. Lost (email if found).&lt;/li&gt;&lt;li&gt;Save the script somewhere where you will not delete it and mark it as executable (&lt;code&gt;chmod u+x /path/to/script.sh&lt;/code&gt;)&lt;/li&gt;&lt;li&gt;Add the script to the startup programs with the argument &lt;code&gt;init&lt;/code&gt; (i.e. &lt;code&gt;/path/to/script.sh init&lt;/code&gt;). If you don’t know how to add startup programs in your Desktop Environment, Google it.&lt;/li&gt;&lt;li&gt;Assign F13 (the now remapped capslock key), as a hotkey in your window manager. Set the command to &lt;code&gt;'/path/to/script.sh toggle'&lt;/code&gt;. Again, if you don’t know how to add a hotkey, Google it.&lt;/li&gt;&lt;li&gt;Now either log out and then in or run ’&lt;code&gt;/path/to/script.sh init'&lt;/code&gt; in order to remap the capslock key.&lt;/li&gt;&lt;li&gt;Pressing the capslock key should now toggle navigation mode.&lt;/li&gt;&lt;/ol&gt;</content></entry><entry><id>https://stebalien.com/blog/bash-completion-with-aptitude-aliases/</id><title>Bash completion with aptitude aliases</title><link href="https://stebalien.com/blog/bash-completion-with-aptitude-aliases/" rel="alternate"/><updated>2009-05-24T00:00:00+00:00</updated><published>2009-05-24T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;Problem&lt;/h2&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;If I used my alias for installing an application (&lt;code&gt;inst application&lt;/code&gt;), I would have to know the full application name because I would not have bash completion. This was very annoying when installing libraries because they often have weird version strings tacked on to their ends.&lt;/li&gt;&lt;li&gt;If I used the full command (&lt;code&gt;sudo aptitude install application&lt;/code&gt;), I would have bash completion and would therefore not have to know the whole application name. I could simply type &lt;code&gt;libxul&lt;/code&gt; and get &lt;code&gt;libxul0d&lt;/code&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;On one hand, I would type a short command plus a complected application name, on the other I would type a long command and a simple application name. I wanted to be able to type a short command with a simple application name.&lt;/p&gt;&lt;h2&gt;Solution&lt;/h2&gt;&lt;p&gt;I wrote my own bash completion rules. They are based on the default aptitude bash completion rules but customized for aliases.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;# Install Completion
#
_aptitude_all()
{
        local cur dashoptions

        COMPREPLY=()
        cur=`_get_cword`

        dashoptions='-S -u -i -h --help --version -s --simulate -d \
                     --download-only -P --prompt -y --assume-yes -F \
                     --display-format -O --sort -w --width -f -r -g \
                     --with-recommends --with-suggests -R -G \
                     --without-recommends --without-suggests -t \
                     --target-release -V --show-versions -D --show-deps\
                     -Z -v --verbose --purge-unused'

        if [[ &amp;quot;$cur&amp;quot; == -* ]]; then
            COMPREPLY=( $( compgen -W &amp;quot;$dashoptions&amp;quot; -- $cur ) )
        else
                COMPREPLY=( $( apt-cache pkgnames $cur 2&amp;gt; /dev/null ) )
        fi
        return 0
}
_aptitude_installed()
{
        local cur dashoptions

        COMPREPLY=()
        cur=`_get_cword`

        dashoptions='-S -u -i -h --help --version -s --simulate -d \
                     --download-only -P --prompt -y --assume-yes -F \
                     --display-format -O --sort -w --width -f -r -g \
                     --with-recommends --with-suggests -R -G \
                     --without-recommends --without-suggests -t \
                     --target-release -V --show-versions -D --show-deps\
                     -Z -v --verbose --purge-unused'

        if [[ &amp;quot;$cur&amp;quot; == -* ]]; then
            COMPREPLY=( $( compgen -W &amp;quot;$dashoptions&amp;quot; -- $cur ) )
        else
                COMPREPLY=( $( _comp_dpkg_installed_packages $cur ) )
        fi
        return 0
}
complete -F _aptitude_all $default inst
complete -F _aptitude_all $default upgrade
complete -F _aptitude_all $default apt-info
complete -F _aptitude_all $default apt-changes
complete -F _aptitude_all $default apt-download
complete -F _aptitude_installed $default uninst
complete -F _aptitude_installed $default reinst
complete -F _aptitude_installed $default purge
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Just copy it into a file such as &lt;code&gt;~/.bash_completion&lt;/code&gt; and source the file in your ~/.bashrc by adding “&lt;code&gt;. ~/.bash_completion&lt;/code&gt;”.&lt;/p&gt;&lt;p&gt;Change/Add/Remove the aliases at the end of the file. The lines that start with &lt;code&gt;complete -F _aptitude_all&lt;/code&gt; complete any available or installed package and lines that start with &lt;code&gt;complete -F _aptitude_installed&lt;/code&gt; complete only installed packages.&lt;/p&gt;&lt;p&gt;inst, upgrade, apt-info, apt-changes…. are my aliases. You must use YOUR ALIASES for this to work. To add aliases, read &lt;a href=&quot;http://blogs.techrepublic.com.com/10things/?p=352&quot;&gt;this&lt;/a&gt;.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/how-to-invite-someone-to-ubuntu-one/</id><title>How to invite someone to Ubuntu One</title><link href="https://stebalien.com/blog/how-to-invite-someone-to-ubuntu-one/" rel="alternate"/><updated>2009-05-24T00:00:00+00:00</updated><published>2009-05-24T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;For anyone with an Ubuntu One account, the following are instructions for sending invites:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Go to &lt;a href=&quot;https://ubuntuone.com/files/#path=/My%20Files&quot;&gt;My Files&lt;/a&gt; in the web interface.&lt;/li&gt;&lt;li&gt;Create a new folder (to be shared) with the person that you will be inviting&lt;/li&gt;&lt;li&gt;Go to the sharing tab (on the right).&lt;/li&gt;&lt;li&gt;Share the folder with the person that you want to invite (The trick is that someone does not have to have an Ubuntu One account to accept a share).&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;a href=&quot;http://ubuntuone.com/&quot;&gt;http://ubuntuone.com&lt;/a&gt;&lt;/p&gt;&lt;p&gt;For anyone looking for an invite, just ask @&lt;a href=&quot;http://identi.ca/notice/new?replyto=bugabundo&quot;&gt;bugabundo&lt;/a&gt; at Identi.ca.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/grdc-very-good-graphical-vnc-viewer-for/</id><title>Grdc - A very good graphical VNC viewer for the average linux user</title><link href="https://stebalien.com/blog/grdc-very-good-graphical-vnc-viewer-for/" rel="alternate"/><updated>2009-05-20T00:00:00+00:00</updated><published>2009-05-20T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;Overview&lt;/h2&gt;&lt;p&gt;After finding myself frustrated with &lt;a href=&quot;http://projects.gnome.org/vinagre/&quot;&gt;Vinagre&lt;/a&gt;’s (GNOME’s standard VNC viewer) lack of ssh tunneling support I began looking for an alternative. I finally found it: &lt;a href=&quot;http://grdc.sourceforge.net/&quot;&gt;Grdc&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Grdc supports both VNC and RDP. Grdc can tunnel both connections over ssh for security. The ssh tunneling supports key based or password based authentication and allows the user to select a custom server and port (if they differ from the defaults).&lt;/p&gt;&lt;p&gt;Grdc’s user interface is much simpler than that of Vinagre. It allows the user to create and save custom connections and organize them by groups. Its connection quality settings are, unlike many remote desktop programs, quite simple. The user simply chooses the quality and the number of colors. Grdc also has a panel applet for the gnome-panel that lets users connect to their saved connections through a simple menu.&lt;/p&gt;&lt;p&gt;Another interesting feature is the “VNC Incoming Connection” protocol. This protocol lets users create new VNC servers with custom ports and passwords on the fly. This is especially useful if a user wants to let a remote user temporarily control their desktop.&lt;/p&gt;&lt;p&gt;Grdc does not support everything. It does not support tabbed connections like Vinagre but, in my opinion, tabbed connections are overkill for most users. It also does not support browsing a network for VNC connections with avahi as Vinagre does. This feature would be useful for administrators but is somewhat useless for the average user because most users will access the computer from the internet, not the LAN, and thus avahi support would be pointless.&lt;/p&gt;&lt;p&gt;Overall, Grdc is good for remote, internet traversing connections because of its ssh support and is useful to the average user because of its simplicity. Vinagre is still a better VNC client for administrators that have to manage many computers over a LAN because, on a LAN, encryption is usually unnecessary while avahi support is helpful and, when managing multiple computers, tabs are very helpful.&lt;/p&gt;&lt;p&gt;For screenshots see the &lt;a href=&quot;http://sourceforge.net/projects/grdc/#screenshots&quot;&gt;SourceForge Screenshot Page&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;Installation&lt;/h2&gt;&lt;p&gt;As of the writing of this post, the version of Grdc in the ubuntu repositories is woefully out of date and does not support ssh tunneling. Therefore one should use the deb provided by the project maintainers on sourceforge. Simply download the grdc deb (link below) and install (usually just double click). The grdc-gnome package is the gnome-panel applet and the grdc package is the main program.&lt;/p&gt;&lt;p&gt;Downloads: &lt;a href=&quot;http://sourceforge.net/project/showfiles.php?group_id=248852&quot;&gt;http://sourceforge.net/project/showfiles.php?group_id=248852&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Homepage: &lt;a href=&quot;http://grdc.sourceforge.net/&quot;&gt;http://grdc.sourceforge.net/&lt;/a&gt;&lt;/p&gt;&lt;p&gt;– Edit: I have packaged a 64bit version. Download &lt;a href=&quot;https://stebalien.com/blog/grdc-very-good-graphical-vnc-viewer-for/static/grdc_0.5.1%7Eppa1_amd64.deb&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;– Edit 2: The 64bit version of grdc-gnome (the panel applet) for karmic seems to work in jaunty. Download &lt;a href=&quot;http://packages.ubuntu.com/karmic/grdc-gnome&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/howto-ones-linux-computer-with/</id><title>How to record one's linux computer with pulseaudio</title><link href="https://stebalien.com/blog/howto-ones-linux-computer-with/" rel="alternate"/><updated>2009-03-21T00:00:00+00:00</updated><published>2009-03-21T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I posted a simple tip to an Identi.ca user (mxc) explaining how to record a skype conversation and felt that others might find the information useful. Here is an elaborated explanation of how to record the sound from a linux computer.&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Install &lt;a href=&quot;apt:pavucontrol&quot;&gt;pavucontrol&lt;/a&gt; and the gnome-sound-recorder (in the &lt;a href=&quot;apt:gnome-media&quot;&gt;gnome-media&lt;/a&gt; package).&lt;/li&gt;&lt;li&gt;Open the gnome-sound-recorder and start recording&lt;/li&gt;&lt;li&gt;Open the pulseaudio volume control and switch to the recording tab&lt;/li&gt;&lt;li&gt;Click on the down arrow of the “gnome-sound-recorder” Record Stream, Select “Move Stream” and move the stream to the “Monitor” stream for your sound card.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This should record all sound from your computer if you are using pulseaudio. I have not tested this with skype but it should work.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/identica-feed-fetcher-script/</id><title>Identi.ca feed fetcher script</title><link href="https://stebalien.com/blog/identica-feed-fetcher-script/" rel="alternate"/><updated>2009-03-11T00:00:00+00:00</updated><published>2009-03-11T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Attached is a simple Identi.ca feed fetcher script written in python. It is a console program and has no gui. It will display the same content as would appear on a users Identi.ca homepage for a given user. Edit the configuration at the head of the script before use.&lt;/p&gt;&lt;p&gt;It works well with conky; simply add &lt;code&gt;${exec python /path/to/script.py}&lt;/code&gt; to your conky file and it will update whenever your conky refreshes. &lt;strong&gt;If your conky config is set to update very frequently, please use &lt;code&gt;${execi interval /path/to/script.py}&lt;/code&gt; so that you are not constantly polling identi.ca’s server.&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Edit 1: Updated script to correctly encode text and to include replies.&lt;/li&gt;&lt;li&gt;Edit 2: Inline script to fix broken link.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Script:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (C) 2008 Steven
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.

# Blog: http://blog.stebalien.com

import feedparser
import textwrap
import unicodedata
### SETTINGS ###
# Username (lowercase)
USER = &amp;quot;stebalien&amp;quot;
# Width in charictars of output
WIDTH = 50
# Number of posts to display
POSTS = 10

# Indent of posts
INDENT_FIRST = 3
INDENT_REST = 5

################

def formatPost(string):
    #string = string..replace(unicode(&amp;quot;“&amp;quot;, &amp;quot;utf-8&amp;quot;), '&amp;quot;').replace(unicode(&amp;quot;”&amp;quot;, &amp;quot;utf-8&amp;quot;), '&amp;quot;').replace(unicode(&amp;quot;…&amp;quot;, &amp;quot;utf-8&amp;quot;), '...').replace(unicode(&amp;quot;’&amp;quot;, &amp;quot;utf-8&amp;quot;), &amp;quot;'&amp;quot;)
    string = unicodedata.normalize('NFKD', string).encode('ascii','ignore')
    parts = string.partition(':')
    return parts[0] + parts[1] \
    + &amp;quot;\n&amp;quot; + wrapper.fill('\n' \
    + parts[2]) \
    + &amp;quot;\n\n&amp;quot;

wrapper = textwrap.TextWrapper()
wrapper.subsequent_indent = ' '*INDENT_FIRST
wrapper.initial_indent = ' '*INDENT_REST
wrapper.width = WIDTH
# Get and display feed
all_feed = feedparser.parse(&amp;quot;http://identi.ca/&amp;quot; + USER + &amp;quot;/all/rss&amp;quot;)
reply_feed = feedparser.parse(&amp;quot;http://identi.ca/&amp;quot; + USER + &amp;quot;/replies/rss&amp;quot;)

n = 0
i = 0

while ((i+n) &amp;lt; POSTS):
    while (reply_feed.entries[n].updated_parsed &amp;gt; all_feed.entries[i].updated_parsed):
        print &amp;quot;&amp;gt; &amp;quot; + formatPost(reply_feed.entries[n].title)
        n=n+1
    print &amp;quot;  &amp;quot; + formatPost(all_feed.entries[i].title)
    i=i+1
&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><id>https://stebalien.com/blog/mumbles-screenshot/</id><title>Mumbles screenshot</title><link href="https://stebalien.com/blog/mumbles-screenshot/" rel="alternate"/><updated>2008-11-20T00:00:00+00:00</updated><published>2008-11-20T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Here is a screenshot of mumbles.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/mumbles-screenshot/static/screenshot.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/mumbles-screenshot/static/screenshot.th.png&quot; alt=&quot;Mumbles Screenshot&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/banshee-plugin-for-mumbles/</id><title>Banshee Plugin for mumbles</title><link href="https://stebalien.com/blog/banshee-plugin-for-mumbles/" rel="alternate"/><updated>2008-11-20T00:00:00+00:00</updated><published>2008-11-20T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I Just wrote a banshee plugin for mumbles. It works much better than banshee’s default notification daemon integration. Clicking on the notification brings banshee to the front. Install it like you would any other mumbles plugin (see two posts back or look for instructions on the mumbles &lt;a href=&quot;https://github.com/xiongchiamiov/mumbles&quot;&gt;homepage&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;Plugin (python 2.5) + Source: &lt;a href=&quot;https://stebalien.com/blog/banshee-plugin-for-mumbles/static/banshee-plugin_mumbles.tar.gz&quot;&gt;banshee-plugin_mumbles.tar.gz&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/change-notification-icon-in-mumbles/</id><title>Change the notification icon in Mumbles without creating a plugin.</title><link href="https://stebalien.com/blog/change-notification-icon-in-mumbles/" rel="alternate"/><updated>2008-11-17T00:00:00+00:00</updated><published>2008-11-17T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I have noticed that several people have asked how to change the notification icon on mumbles without creating a new plugin.This is actually very simple: use the generic DBus plugin with dbus-send. I believe that this tutorial requires the SVN version but I may be mistaken. First add a new section to your DBus plugin configuration file. If you do not have this configuration file, copy the sample one from the mumbles source tarball (found on the project’s homepage). The file is called &lt;code&gt;dbus.conf.sample&lt;/code&gt;. Copy this file to &lt;code&gt;~/.mumbles/dbus.conf&lt;/code&gt;. The new section is as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[program-or-script-name]
enabled = true
interface = org.program-or-script-name.DBus
path = /org/program-or-script-name/DBus
signal = your-signal
icon = your-icon.png
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Put &lt;em&gt;your-icon.png&lt;/em&gt; in the &lt;code&gt;~/.mumbles/plugins/icons/&lt;/code&gt; folder.&lt;em&gt;your-signal&lt;/em&gt; is your name for the alert. None of these variables are important but they must be consistent.&lt;/p&gt;&lt;p&gt;After doing the previous and restarting mumbles, run this command:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;dbus-send /org/program-or-script-name/DBus org.`*program-or-script-name*`.DBus.your-signal string:&amp;quot;Title&amp;quot; string:&amp;quot;Message&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Make sure that you replace all necessary variables. This will display a mumbles popup with your custom icon, title, and message. You can also specify a click action using &lt;code&gt;launch = action&lt;/code&gt;.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/mumbles/</id><title>Mumbles with gmail</title><link href="https://stebalien.com/blog/mumbles/" rel="alternate"/><updated>2008-11-16T00:00:00+00:00</updated><published>2008-11-16T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I have been playing around with &lt;a href=&quot;https://github.com/xiongchiamiov/mumbles&quot;&gt;mumbles&lt;/a&gt; lately and have written a gmail plugin for anyone interested. Mumbles is a notification system that works very much like growl for the mac. The latest SVN version can even replace the gnome notification daemon (but not very well). While still in its infancy, this project shows a lot of promise. I based the plugin on the evolution tutorial on project’s homepage. I based the script on &lt;a href=&quot;https://g33k.wordpress.com/2009/02/04/check-gmail-the-python-way/&quot;&gt;this&lt;/a&gt; script (with many modifications). When you click on the notification popup, evolution will launch (you can edit the source to change the program). The script requires &lt;a href=&quot;apt:python-feedparser&quot;&gt;python-feedparser&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Plugin, Source, and Script package: &lt;a href=&quot;https://stebalien.com/blog/mumbles/static/gmail-plugin_mumbles.tar.gz&quot;&gt;gmail-plugin_mumbles.tar.gz&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Installing the precompiled plugin:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Do step 2 from the next section. Now copy the plugin to
&lt;code&gt;~/.mumbles/plugins/&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Installing the plugin from source (adapted from dot_j’s &lt;s&gt;&lt;a href=&quot;http://www.mumbles-project.org/2007/09/03/tutorial-write-an-evolution-plugin/&quot;&gt;tutorial&lt;/a&gt;&lt;/s&gt; (gone)):&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;1. Download the source&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;Download the package and extract. Then open a terminal window inside the plugin-source folder.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. Create the directory structure and choose an icon&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Run the following command:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;mkdir -p ~/.mumbles/plugins/icons
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then either run this to use the default evolution icon…&lt;/p&gt;&lt;pre&gt;&lt;code&gt;cp /usr/share/icons/hicolor/22×22/apps/evolution.png ~/.mumbles/plugins/icons
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or just copy your desired icon into the plugins/icons folder and rename it to &lt;code&gt;evolution.png&lt;/code&gt;. This is unnecessary if you already have the evolution plugin.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Build and install the plugin&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;cd ~/gmail_plugin
python setup.py bdist_egg
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will use our &lt;code&gt;setup.py&lt;/code&gt; file to create our plugin. After it runs, you should see a file named &lt;code&gt;GmailMumbles-0.1-py2.5.egg&lt;/code&gt; in the dist directory that was created by the build process. Here the &lt;code&gt;-py2.5&lt;/code&gt; part of the filename refers to what version of python you are using (it may vary, but if you are able to start mumbles, should not matter).&lt;/p&gt;&lt;p&gt;The .egg file is our plugin, so the only thing left to do is copy that to the mumbles plugin directory.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;cp dist/GmailMumbles-0.1-py2.5.egg ~/.mumbles/plugins
&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Installing the script:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Find the script in the package and edit the username and password variables. Run this script with &lt;code&gt;python /path/to/gmail.py&lt;/code&gt; (replace /path/to with the path to the script). By default, this script will check for new email every 2 minutes and alert the user of new mail.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;BTW: This script does not safely store the username and password. I may, in the distant future, make this script work with gnome-keyring but this is not likely. I hope that mumbles will soon get better notification-daemon support so that I can go back to using &lt;a href=&quot;apt:mail-notification&quot;&gt;mail-notification&lt;/a&gt;.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/openid-more-accepters-less-providers/</id><title>OpenID: More accepters, less providers.</title><link href="https://stebalien.com/blog/openid-more-accepters-less-providers/" rel="alternate"/><updated>2008-10-29T00:00:00+00:00</updated><published>2008-10-29T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I am noticing a very annoying and self-defeating trend with OpenIDs. The main purpose of OpenID is to allow people to have one login for multiple sites. OpenID is pointless if few sites accept it. This problem will correct itself eventually as more sites adopt OpenID. The biggest problem is the number of sites providing and not accepting OpenID. This defeats the whole purpose of OpenID. It is pointless to have a myriad of choices for OpenIDs if you can’t use them anywhere. Multiple OpenIDs for multiple sites just means multiple logins which is the very thing that OpenID is supposed to fix. I hope that many sites *ahem blogger* will soon start accepting OpenIDs as login credentials instead of just providing them.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/general-bug-reporting-rant/</id><title>General bug reporting rant</title><link href="https://stebalien.com/blog/general-bug-reporting-rant/" rel="alternate"/><updated>2008-09-19T00:00:00+00:00</updated><published>2008-09-19T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I am starting to see this very often and find it very annoying: people report a bug, later they find a preexisting report for the same bug, they then mark that preexisting bug as a duplicate of their bug. I end up getting lots of “this bug has been marked as a duplicate of bug x” emails and this is VERY annoying. To prevent this people need to rigorously check for a preexisting report. Second, when you find two duplicate bugs, search the bug tracker for any more duplicates and mark all of the newest duplicates as duplicates of the oldest bug. This prevents chains of duplicates which in turn maximizes the efficiency of the bug reporting and fixing process.&lt;/p&gt;&lt;p&gt;Example:&lt;/p&gt;&lt;p&gt;Chained:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-text&quot;&gt; report 1     -&amp;gt;    report 2     -&amp;gt;     report 3     -&amp;gt;     report 4
    |                  |                   |                   |
Commenters         Commenters           Commenters         All commenters
from report 1      from reports 1-2     from report 1-3
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Non-chained:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-text&quot;&gt;report 1 ----\
 commenters 1 \
report 2 ------&amp;gt; report 4
 commenters 2 /   all commenters
report 3 ----/
 commenters 3
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the chained version commenters move from report to report wasting comments on bugs that will probably never be seen again after being marked a duplicate. In the non-chained version commenters move directly from the duplicate to the final (and oldest) bug report wasting as few as comments possible on the duplicates.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/linux-razer-lycosa-wtf/</id><title>Linux + Razer Lycosa = WTF</title><link href="https://stebalien.com/blog/linux-razer-lycosa-wtf/" rel="alternate"/><updated>2008-09-06T00:00:00+00:00</updated><published>2008-09-06T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I broke my previous keyboard and went shopping for a new one (FYI: water + G15 == not good). I wanted another back-lit keyboard (like my previous G15). When I went to Best Buy (not my favorite store) I saw the slick Raser Lycosa Keyboard I decided to try it. My advice: don’t. While the keyboard itself is great (I like the low, rubber covered keys) the neither the the USB port nor the headphone jack worked on either of the keyboards that I tried (I returned the first one and tried the second one in the store). The problem: Interference. My computer refused to mount my USB flash drive while plugged into the Lycosa and the headphones buzzed (like a radio). I ended up getting the latest (annoyingly orange) G15 keyboard which works but I HATE ORANGE.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/inline-comments/</id><title>Inline Comments</title><link href="https://stebalien.com/blog/inline-comments/" rel="alternate"/><updated>2008-08-18T00:00:00+00:00</updated><published>2008-08-18T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I Finaly got around to enabling inline comments. For help on this, follow this &lt;a href=&quot;http://www.bloggerbuster.com/2008/06/how-to-add-comment-form-beneath-your.html&quot;&gt;tutorial&lt;/a&gt;.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/vcard-viewer-perl-script/</id><title>vCard viewer perl script</title><link href="https://stebalien.com/blog/vcard-viewer-perl-script/" rel="alternate"/><updated>2008-06-27T00:00:00+00:00</updated><published>2008-06-27T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I needed a simple way to view vCards in mutt and found a few Perl
scripts that could parse and display vCards but none of them worked very
well. I searched the ubuntu repository, found &lt;code&gt;libtext-vcard-perl&lt;/code&gt;, and
wrote a simple Perl script using this library.&lt;/p&gt;&lt;blockquote class=&quot;warning&quot;&gt;&lt;p&gt;I know almost no Perl. This script will not corrupt nor will it delete any of your files but it may fail to correctly display vCards. It is very unlikely that I will ever update this script.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Past this in &lt;code&gt;vcard-view.pl&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-perl&quot;&gt;#!/usr/bin/perl
# Copyright (C) 2008, Steven Allen
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see //www.gnu.org/licenses/&amp;gt;.

# This program parses vCards and displays their contents in a
# human readable form.

# Import libtext-vcard-perl library
use Text::vCard::Addressbook;
use Text::vCard::Node;
use Text::vCard;

# Get card name
my $card_file = $ARGV[0];

# Create Addressbook
my $address_book = Text::vCard::Addressbook-&amp;gt;new({
 'source_file' =&amp;gt; $card_file,
});

#Loop through Addressbook
foreach my $vcard ($address_book-&amp;gt;vcards()) {
 print &amp;quot;=&amp;quot; . ( &amp;quot;=&amp;quot; x 59 ) . &amp;quot;\n&amp;quot;;

 # Name/Email
 print &amp;quot;Name:\n &amp;quot; . $vcard-&amp;gt;fullname();
 $count = 0;
 my $emails = $vcard-&amp;gt;get({ 'node_type' =&amp;gt; 'EMAIL' });
 foreach my $email (@{$emails}) {
  if ($count == 0) {
   $space = &amp;quot; &amp;quot;;
  } else {
   $space = &amp;quot;  &amp;quot; . ( &amp;quot; &amp;quot; x length ($vcard-&amp;gt;fullname()) );
  }
  if($email-&amp;gt;is_type('home')) {
   print $space . &amp;quot;&amp;quot; . $email-&amp;gt;value() . &amp;quot;&amp;gt; (Home)\n&amp;quot;;
  } elsif ($email-&amp;gt;is_type('work')) {
   print $space . &amp;quot;&amp;quot; . $email-&amp;gt;value() . &amp;quot;&amp;gt; (Work)\n&amp;quot;;
  } else {
   print $space . &amp;quot;&amp;quot; . $email-&amp;gt;value() . &amp;quot;&amp;gt;\n&amp;quot;;
  }
  $count++;
 }

 # Address
 my $addresses = $vcard-&amp;gt;get({ 'node_type' =&amp;gt; 'addresses' });
 print &amp;quot;\nAddresses:\n&amp;quot;;
 foreach my $address (@{$addresses}) {
  if($address-&amp;gt;is_type('home')) {
   print &amp;quot; --------------------------------\n&amp;quot;;
   print &amp;quot;\t&amp;quot; . $address-&amp;gt;street() . &amp;quot;\n&amp;quot;;
   print &amp;quot; Home:\t&amp;quot; . $address-&amp;gt;city() . &amp;quot;, &amp;quot; . $address-&amp;gt;region() . &amp;quot;\n&amp;quot;;
   print &amp;quot;\t&amp;quot; . $address-&amp;gt;post_code() . &amp;quot;, &amp;quot; . $address-&amp;gt;country() . &amp;quot;\n&amp;quot;;
   print &amp;quot; --------------------------------\n&amp;quot;;
  } elsif ($address-&amp;gt;is_type('work')) {
   print &amp;quot; --------------------------------\n&amp;quot;;
   print &amp;quot;\t&amp;quot; . $address-&amp;gt;street() . &amp;quot;\n&amp;quot;;
   print &amp;quot; Work:\t&amp;quot; . $address-&amp;gt;city() . &amp;quot;, &amp;quot; . $address-&amp;gt;region() . &amp;quot;\n&amp;quot;;
   print &amp;quot;\t&amp;quot; . $address-&amp;gt;post_code() . &amp;quot;, &amp;quot; . $address-&amp;gt;country() . &amp;quot;\n&amp;quot;;
   print &amp;quot; --------------------------------\n&amp;quot;;
  } else {
   print &amp;quot; --------------------------------\n&amp;quot;;
   print &amp;quot;\t&amp;quot; . $address-&amp;gt;street() . &amp;quot;\n&amp;quot;;
   print &amp;quot; Other:\t&amp;quot; . $address-&amp;gt;city() . &amp;quot;, &amp;quot; . $address-&amp;gt;region() . &amp;quot;\n&amp;quot;;
   print &amp;quot;\t&amp;quot; . $address-&amp;gt;post_code() . &amp;quot;, &amp;quot; . $address-&amp;gt;country() . &amp;quot;\n&amp;quot;;
   print &amp;quot; --------------------------------\n&amp;quot;;
  }
 }
 # Phones
 print &amp;quot;Phones:\n&amp;quot;;
 my $tels = $vcard-&amp;gt;get({ 'node_type' =&amp;gt; 'tel' });
 foreach my $tel (@{$tels}) {
  if($tel-&amp;gt;is_type('home')) {
   print &amp;quot; Home:\t&amp;quot; . $tel-&amp;gt;value() . &amp;quot;\n&amp;quot;;
  } elsif ($tel-&amp;gt;is_type('cell')) {
   print &amp;quot; Cell:\t&amp;quot; . $tel-&amp;gt;value() . &amp;quot;\n&amp;quot;;
  } elsif ($tel-&amp;gt;is_type('work')) {
   print &amp;quot; Work:\t&amp;quot; . $tel-&amp;gt;value() . &amp;quot;\n&amp;quot;;
  } else {
   print &amp;quot; Other:\t&amp;quot; . $tel-&amp;gt;value() . &amp;quot;\n&amp;quot;;
  }
 }
 # URL
 my $urls = $vcard-&amp;gt;get({ 'node_type' =&amp;gt; 'URL' });
 foreach my $url (@{$urls}) {
  print &amp;quot;Web:\t&amp;quot; . $url-&amp;gt;value() . &amp;quot;\n&amp;quot;;
 }
 print &amp;quot;=&amp;quot; . ( &amp;quot;=&amp;quot; x 59 ) . &amp;quot;\n&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><id>https://stebalien.com/blog/running-system-panel-on-64-bit-machine/</id><title>Running the &quot;Ubunu System Panel&quot; on a 64 bit machine.</title><link href="https://stebalien.com/blog/running-system-panel-on-64-bit-machine/" rel="alternate"/><updated>2008-06-19T00:00:00+00:00</updated><published>2008-06-19T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;After installing the 64 bit version of Ubuntu on my Dell Inspiron 1420n I found that the USP (Ubuntu System Panel) keybindings do not work. This is because the &lt;code&gt;/usr/lib/python2.4/site-packages/usp/plugins/_keybinder.so&lt;/code&gt; is compiled for a 32 bit system. After a little searching I found out that &lt;a href=&quot;apt:glipper&quot;&gt;Glipper&lt;/a&gt; includes a &lt;code&gt;_keybinder.so&lt;/code&gt; file. After replacing USP’s _keybinder.so with Glipper’s version, the keybindings worked fine.&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Download glipper &lt;code&gt;aptitude download glipper&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Extract the glipper deb with &lt;code&gt;dpkg-deb -x glipper_1.0-1ubuntu1_amd64.deb glipper&lt;/code&gt; (change
glipper_1.0-1ubuntu1_amd64.deb to the name of your file).&lt;/li&gt;&lt;li&gt;Copy in the new _keybinder.so with &lt;code&gt;sudo cp ./glipper/usr/lib/python-support/glipper/python2.5/glipper/keybinder/_keybinder.so /usr/lib/python2.4/site-packages/usp/plugins/_keybinder.so&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Restart USP with &lt;code&gt;killall gnome-panel&lt;/code&gt; and your USP shortcut keys should now work.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Edit: This is no longer necessary.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/latte-rant/</id><title>Latte rant</title><link href="https://stebalien.com/blog/latte-rant/" rel="alternate"/><updated>2008-06-19T00:00:00+00:00</updated><published>2008-06-19T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote class=&quot;important&quot;&gt;&lt;p&gt;I usually don’t correct my old blog posts from when I was in high school but… this one offends my coffee sensibilities. There’s no such thing as an espresso roast; the issue I described below is under-extraction. This is usually caused by not grinding the coffee fine enough, old beans, and/or not putting enough coffee in the portafilter.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I religiously drink decaf lattes (by “religiously” I mean every Sunday) and am becoming very annoyed with the common misconception that lattes are just coffee and milk. True lattes are not made with standard coffee beans, they are made with espresso beans. Espresso beans are roasted longer and thus are much richer but less bitter. Many so called coffee shops claim to sell lattes but use regular coffee beans in their espresso machine. The resulting drink is not a café latte. It is called a café au lait.&lt;/p&gt;&lt;p&gt;Here’s my plea: Coffee shops, please correctly label your drinks.&lt;/p&gt;&lt;p&gt;PS: If you go to Canada, bring your own espresso. I can’t find a decent Latte in all of Nova Scotia.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/new-compiz-transparency/</id><title>New Compiz transparency</title><link href="https://stebalien.com/blog/new-compiz-transparency/" rel="alternate"/><updated>2008-06-07T00:00:00+00:00</updated><published>2008-06-07T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;This may not be new but, after upgrading compiz from git today, I noticed that certain windows (gnome-system-monitor and the AWN manager) were semi-transparent. See the screenshot:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/new-compiz-transparency/static/screenshot.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/new-compiz-transparency/static/screenshot.th.png&quot; alt=&quot;Semi-Transparent Compiz Windows&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Edit: As far as I can tell, the Murrine engine, not compiz, is not causing the transparency.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/conky-simple-system-monitor/</id><title>Conky: The simple system monitor</title><link href="https://stebalien.com/blog/conky-simple-system-monitor/" rel="alternate"/><updated>2008-06-02T00:00:00+00:00</updated><published>2008-06-02T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;apt:conky&quot;&gt;Conky&lt;/a&gt; is a simple system monitor that uses very little ram.&lt;/p&gt;&lt;p&gt;I have 2 conky-rc files: a system monitor and a weather display.&lt;/p&gt;&lt;p&gt;I wrote my system monitor from scratch and my weather display is based on &lt;a href=&quot;http://ubuntuforums.org/showthread.php?t=760527&quot;&gt;this tutorial&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;System Monitor conkyrc&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/conky-simple-system-monitor/static/screenshot1.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/conky-simple-system-monitor/static/screenshot1.th.png&quot; alt=&quot;system monitor&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-conky&quot;&gt;# System Monitor
# Stebalien.com
# set to yes if you want Conky to be forked in the background
background no

# Use Xft?
use_xft yes

# Xft font when Xft is enabled
xftfont Bitstream Vera Sans Mono:size=8

# Text alpha when using Xft
xftalpha 0.8

# Update interval in seconds
update_interval 5.0

# This is the number of times Conky will update before quitting.
# Set to zero to run forever.
total_run_times 0

# Create own window instead of using desktop (required in nautilus)
own_window yes

# If own_window is yes, you may use type normal, desktop or override
own_window_type override

# Use pseudo transparency with own_window?
own_window_transparent yes

# If own_window_transparent is set to no, you can set the background colour here
own_window_colour black

# If own_window is yes, these window manager hints may be used
#own_window_hints undecorated,below,sticky,skip_taskbar,skip_pager

# Use double buffering (reduces flicker, may not work for everyone)
double_buffer yes

# Minimum size of text area
minimum_size 280 5

# Draw shades?
draw_shades yes

# Draw outlines?
draw_outline no

# Draw borders around text
draw_borders no

# Draw borders around graphs
draw_graph_borders yes

# Stippled borders?
stippled_borders 8

# border margins
border_margin 4

# border width
border_width 1

# Default colors and also border colors
default_color white
default_shade_color black
default_outline_color black

# Text alignment, other possible values are commented
#alignment top_left
#alignment top_right
alignment bottom_left
#alignment bottom_right
#alignment none

# Gap between borders of screen and text
# same thing as passing -x at command line
gap_x 12
gap_y 12

# Subtract file system buffers from used memory?
no_buffers yes

# set to yes if you want all text to be in uppercase
uppercase no

# number of cpu samples to average
# set to 1 to disable averaging
cpu_avg_samples 2

# number of net samples to average
# set to 1 to disable averaging
net_avg_samples 2

# Force UTF8? note that UTF8 support required XFT
override_utf8_locale no

# Add spaces to keep things from moving about?  This only affects certain objects.
use_spacer no

color1 SteelBlue4
TEXT
${color1}${tcp_portmon 50005 50005 lservice 0}          ${color green}${tcp_portmon 50005 50005 rip 0}
${color1}Drives:      ${color lightgrey}Lin:${color}${fs_used_perc /}% | ${color lightgrey}Home:${color}${fs_used_perc /home}% | ${color lightgrey}Ext:${color}${fs_used_perc /media/ext}%
${color1}Network:     ${color lightgrey}Down:$color${downspeed ath0} k/s | ${color lightgrey}Up:$color${upspeed ath0} k/s
${color1}System:      ${color lightgrey}CPU:$color$cpu% | ${color lightgrey}RAM:$color$memperc% | ${color lightgrey}SWAP:$color$swapperc%
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Replace &lt;code&gt;ath0&lt;/code&gt; with the name of your network interface.&lt;/p&gt;&lt;h2&gt;Weather Display conkyrc&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/conky-simple-system-monitor/static/screenshot2.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/conky-simple-system-monitor/static/screenshot2.th.png&quot; alt=&quot;weather panel&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-conky&quot;&gt;# Weather display
# Stebalien.com
# set to yes if you want Conky to be forked in the background
background no

# Use Xft?
use_xft yes

# Xft font when Xft is enabled
#xftfont Bitstream Vera Sans Mono:size=8
#xftfont Terminus:size=8
xftfont Liberation Sans:size=8

# Text alpha when using Xft
xftalpha 0.8

# Print everything to console?
# out_to_console no

# mail spool
#mail_spool $MAIL

# Update interval in seconds
update_interval 5.0

# This is the number of times Conky will update before quitting.
# Set to zero to run forever.
total_run_times 0

# Create own window instead of using desktop (required in nautilus)
own_window no

# Use double buffering (reduces flicker, may not work for everyone)
double_buffer yes

# Minimum size of text area
minimum_size 100 5
maximum_width 600

# Draw shades?
draw_shades no

# Draw outlines?
draw_outline no

# Draw borders around text
draw_borders no
draw_graph_borders yes

# Stippled borders?
stippled_borders 8

# border margins
border_margin 4

# border width
border_width 1

# Default colors and also border colors
default_color white
default_shade_color black
default_outline_color white

# own window options
own_window  yes
own_window_transparent yes
own_window_type  override
own_window_hints undecorated,below,sticky,skip_taskbar,skip_pager

# Text alignment, other possible values are commented
#alignment top_left
#alignment top_right
#alignment bottom_left
alignment bottom_right

# Gap between borders of screen and text
# same thing as passing -x at command line
gap_x 10
gap_y -10

# Subtract file system buffers from used memory?
no_buffers yes

# set to yes if you want all text to be in uppercase
uppercase no

# number of cpu samples to average
# set to 1 to disable averaging
cpu_avg_samples 2

# number of net samples to average
# set to 1 to disable averaging
net_avg_samples 2

# Force UTF8? note that UTF8 support required XFT
override_utf8_locale yes

# Add spaces to keep things from moving about?  This only affects certain objects.
use_spacer yes

# colors
color1 white
# light blue
color2 6892C6
# orange
color3 E77320
# green
color4 78BF39
# red
color5 CC0000
# Steel blue
color6 SteelBlue4

# variable is given either in format $variable or in ${variable}. Latter
# allows characters right after the variable and must be used in network
# stuff because of an argument

# stuff after 'TEXT' will be formatted on screen

TEXT
$color6${hr 1}
 ${voffset -2}Now:
${voffset -6}${hr 1}
   ${voffset -8}$color${font weather:size=64}${execi 3600 python ~/.conky/conkyForecast.py --location=USCA0638 --datatype=WF}${font}
                     ${voffset -66}Conditions: ${execi 3600 python ~/.conky/conkyForecast.py --location=USCA0638 --datatype=CC}
                     Temp: ${execi 3600 python ~/.conky/conkyForecast.py -i --location=USCA0638 --datatype=HT}
                     Wind: ${execi 3600 python ~/.conky/conkyForecast.py -i --location=USCA0638 --datatype=WS} ${execi 3600 python ~/.conky/conkyForecast.py --location=USCA0638 --datatype=WD}
                     Humidity: ${execi 3600 python ~/.conky/conkyForecast.py --location=USCA0638 --datatype=HM}
${voffset -4}$color6${hr 1}
  ${voffset -2}${execi 3600 python ~/.conky/conkyForecast.py --location=USCA0638 --datatype=DW --shortweekday --startday=0 --spaces=9 --endday=4}
${voffset -6}${hr 1}
$color${voffset 4}${font weather:size=24}${execi 3600 python ~/.conky/conkyForecast.py --location=USCA0638 --datatype=WF --startday=0 --endday=4 --spaces=1}${font}
          ${font Liberation Sans:size=6}${voffset -36}${execi 3600 python ~/.conky/conkyForecast.py -i --location=USCA0638 --datatype=HT --startday=0 --endday=4 --spaces=18}
               ${execi 3600 python ~/.conky/conkyForecast.py -i --location=USCA0638 --datatype=LT --startday=0 --endday=4 --spaces=17}
               ${execi 3600 python ~/.conky/conkyForecast.py --location=USCA0638 --datatype=PC --startday=0 --endday=4 --spaces=19}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Replace &lt;code&gt;~/.conky/conkyForecast.py&lt;/code&gt; with the location of your weather
script. Replace &lt;code&gt;USCA0638&lt;/code&gt; with your city code (see the tutorial link at
the beginning of this post).&lt;/p&gt;&lt;h2&gt;Conky weather script&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;#!/usr/bin/python
# -*- coding: utf-8 -*-
###############################################################################
# conkyForecast.py is a (not so) simple (anymore) python script to gather
# details of the current weather for use in conky.
#
#  Author: Kaivalagi
# Created: 13/04/2008
# Modifications:
# 14/04/2008 Allow day ranges for forecast data
# 14/04/2008 Check for connectivity to xoap service
# 18/04/2008 Allow the setting of spaces for ranged output
# 18/04/2008 Allow Night and Day forecast output
# 18/04/2008 Support locale for condition code text &amp;quot;CC&amp;quot; option, awaiting spanish language translation
# 18/04/2008 Use pickling for class data rather than opening xml, this bypasses the need to interrogate cached data
# 19/04/2008 Added spanish condition text - Thanks Bruce M
# 19/04/2008 Added isnumeric check on all numeric output with units suffix
# 19/04/2008 Altered pickle file naming to include location code
# 19/04/2008 Added spanish week days conversion via locale
# 20/04/2008 Added decent command argument parser
# 20/04/2008 Added --shortweekday option, if given the day of week data type is shortened to 3 characters
# 21/04/2008 Fixed locale options for forecast output
# 21/04/2008 Added --template option to allow custom output using a single exec call :)
# 21/04/2008 Added --hideunits option to remove, for example, mph and C from output
# 23/04/2008 Removed --imperial option from template, this MUST be set as a standard option on the script call and not used in the template file.
# 23/04/2008 Readded --imperial option to template, enabling metric or imperial values per datatype. Note when using templates command line option will not work.
# 23/04/2008 Added output notifying user if the location given is bad
# 24/04/2008 Added handling for no connectivity, will revert to cached data now (erroring if no cache exists). Tests by trying to open xoap.weather.com
# 24/04/2008 Fixed Celsius to fahrenheit conversion
# 06/05/2008 Updated url used after webservice was updated
# 09/05/2008 Consolidated current condition and forecast data fetch into one call
# 09/05/2008 Added Sunrise and sunset to datatypes, these are specific to both current conditions and forecast data
# 09/05/2008 Added moon phase, barometer reading and barometer description to datatypes, these are only specific to current conditions and so are N/A in forecasted output
# 09/05/2008 Added unit conversions for barometer from mb to inches (imperial)
#   09/05/2008  Updated spanish condition text - Thanks Bruce M
#   10/05/2008  Added french locale data - Thanks benpaka
#   12/05/2008  Added new BF (bearing font) datatype to provide an arrow character (use with Arrow.ttf font) instead of NSEW output from WD (wind direction)
#   12/05/2008  Updated WD output to be locale specific, currently supports default english and spanish - Thanks Bruce M
# 18/05/2008 Added new MF (moon font) datatype to provide a moon font character (characters incorrect and no dedicated font yet).
# 21/05/2008 For current conditions the --datatype=LT option now displays &amp;quot;feels like&amp;quot; temperature rather than the current temperature
#
# TODO:
# Consolidate pkl files into one file/class
# Add a weather font based moon phase output based on moon icon data
# ??? Any more requirements out there?

import sys, os, socket, urllib2, datetime, time
from xml.dom import minidom
from stat import *
from optparse import OptionParser
import locale
import gettext
import pickle
from math import *

APP=&amp;quot;conkyForecast.py&amp;quot;
DIR=os.path.dirname (__file__) + '/locale'
gettext.bindtextdomain(APP, DIR)
gettext.textdomain(APP)
_ = gettext.gettext

class CommandLineParser:

    parser = None

    def __init__(self):

        self.parser = OptionParser()
        self.parser.add_option(&amp;quot;-l&amp;quot;,&amp;quot;--location&amp;quot;, dest=&amp;quot;location&amp;quot;, default=&amp;quot;UKXX0103&amp;quot;, type=&amp;quot;string&amp;quot;, metavar=&amp;quot;CODE&amp;quot;, help=u&amp;quot;location code for weather data [default: %default],Use the following url to determine your location code by city name: http://xoap.weather.com/search/search?where=Norwich&amp;quot;)
        self.parser.add_option(&amp;quot;-d&amp;quot;,&amp;quot;--datatype&amp;quot;,dest=&amp;quot;datatype&amp;quot;, default=&amp;quot;HT&amp;quot;, type=&amp;quot;string&amp;quot;, metavar=&amp;quot;DATATYPE&amp;quot;, help=u&amp;quot;[default: %default] The data type options are: DW (Day Of Week), WF (Weather Font Output), LT (Forecast:Low Temp,Current:Feels Like Temp), HT (Forecast:High Temp,Current:Current Temp), CC (Current Conditions), CT (Conditions Text), PC (Precipitation Chance), HM (Humidity), WD (Wind Direction), WS (Wind Speed), WG (Wind Gusts), CN (City Name), SR (sunrise), SS (sunset), MP (moon phase), MF (moon font), BR (barometer reading), BD (barometer description). Not applicable at command line when using templates.&amp;quot;)
        self.parser.add_option(&amp;quot;-s&amp;quot;,&amp;quot;--startday&amp;quot;,dest=&amp;quot;startday&amp;quot;, type=&amp;quot;int&amp;quot;, metavar=&amp;quot;NUMBER&amp;quot;, help=u&amp;quot;define the starting day number, if omitted current conditions are output. Not applicable at command line when using templates.&amp;quot;)
        self.parser.add_option(&amp;quot;-e&amp;quot;,&amp;quot;--endday&amp;quot;,dest=&amp;quot;endday&amp;quot;, type=&amp;quot;int&amp;quot;, metavar=&amp;quot;NUMBER&amp;quot;, help=u&amp;quot;define the ending day number, if omitted only starting day data is output. Not applicable at command line when using templates.&amp;quot;)
        self.parser.add_option(&amp;quot;-S&amp;quot;,&amp;quot;--spaces&amp;quot;,dest=&amp;quot;spaces&amp;quot;, type=&amp;quot;int&amp;quot;, default=1, metavar=&amp;quot;NUMBER&amp;quot;, help=u&amp;quot;[default: %default] Define the number of spaces between ranged output. Not applicable at command line when using templates.&amp;quot;)
        self.parser.add_option(&amp;quot;-t&amp;quot;,&amp;quot;--template&amp;quot;,dest=&amp;quot;template&amp;quot;, type=&amp;quot;string&amp;quot;, metavar=&amp;quot;FILE&amp;quot;, help=u&amp;quot;define a template file to generate output in one call. A displayable item in the file is in the form {--datatype=HT --startday=1}. The following are possible options within each item: --datatype,--startday,--endday,--night,--shortweekday,--imperial,--hideunits,--spaces . Note that the short forms of the options are not currently supported! None of these options are applicable at command line when using templates.&amp;quot;)
        self.parser.add_option(&amp;quot;-L&amp;quot;,&amp;quot;--locale&amp;quot;,dest=&amp;quot;locale&amp;quot;, type=&amp;quot;string&amp;quot;, help=u&amp;quot;override the system locale for language output (en=english, es=spanish, fr=french, more to come)&amp;quot;)
        self.parser.add_option(&amp;quot;-i&amp;quot;,&amp;quot;--imperial&amp;quot;,dest=&amp;quot;imperial&amp;quot;, default=False, action=&amp;quot;store_true&amp;quot;, help=u&amp;quot;request imperial units, if omitted output is in metric. Not applicable at command line when using templates.&amp;quot;)
        self.parser.add_option(&amp;quot;-n&amp;quot;,&amp;quot;--night&amp;quot;,dest=&amp;quot;night&amp;quot;, default=False, action=&amp;quot;store_true&amp;quot;, help=u&amp;quot;switch output to night data, if omitted day output will be output. Not applicable at command line when using templates.&amp;quot;)
        self.parser.add_option(&amp;quot;-w&amp;quot;,&amp;quot;--shortweekday&amp;quot;,dest=&amp;quot;shortweekday&amp;quot;, default=False, action=&amp;quot;store_true&amp;quot;, help=u&amp;quot;Shorten the day of week data type to 3 characters. Not applicable at command line when using templates.&amp;quot;)
        self.parser.add_option(&amp;quot;-u&amp;quot;,&amp;quot;--hideunits&amp;quot;,dest=&amp;quot;hideunits&amp;quot;, default=False, action=&amp;quot;store_true&amp;quot;, help=u&amp;quot;Hide units such as mph or C, degree symbols (°) are still shown. Not applicable at command line when using templates.&amp;quot;)
        self.parser.add_option(&amp;quot;-v&amp;quot;,&amp;quot;--verbose&amp;quot;,dest=&amp;quot;verbose&amp;quot;, default=False, action=&amp;quot;store_true&amp;quot;, help=u&amp;quot;request verbose output, no a good idea when running through conky!&amp;quot;)
        self.parser.add_option(&amp;quot;-r&amp;quot;,&amp;quot;--refetch&amp;quot;,dest=&amp;quot;refetch&amp;quot;, default=False, action=&amp;quot;store_true&amp;quot;, help=u&amp;quot;fetch data regardless of data expiry&amp;quot;)

    def parse_args(self):
        (options, args) = self.parser.parse_args()
        return (options, args)

    def print_help(self):
        return self.parser.print_help()

class WeatherData:
    def __init__(self, day_of_week, low, high, condition_code, condition_text, precip, humidity, wind_dir, wind_speed, wind_gusts, city, sunrise, sunset, moon_phase, moon_icon, bar_read, bar_desc):
        self.day_of_week = u&amp;quot;&amp;quot;+day_of_week
        self.low = u&amp;quot;&amp;quot;+low
        self.high = u&amp;quot;&amp;quot;+high
        self.condition_code = u&amp;quot;&amp;quot;+condition_code
        self.condition_text = u&amp;quot;&amp;quot;+condition_text
        self.precip = u&amp;quot;&amp;quot;+precip
        self.humidity = u&amp;quot;&amp;quot;+humidity
        self.wind_dir = u&amp;quot;&amp;quot;+wind_dir
        self.wind_speed = u&amp;quot;&amp;quot;+wind_speed
        self.wind_gusts = u&amp;quot;&amp;quot;+wind_gusts
        self.city = u&amp;quot;&amp;quot;+city
        self.sunrise = u&amp;quot;&amp;quot;+sunrise
        self.sunset = u&amp;quot;&amp;quot;+sunset
        self.moon_phase = u&amp;quot;&amp;quot;+moon_phase
        self.moon_icon = u&amp;quot;&amp;quot;+moon_icon
        self.bar_read = u&amp;quot;&amp;quot;+bar_read
        self.bar_desc = u&amp;quot;&amp;quot;+bar_desc


class WeatherText:

    conditions_text = {
        &amp;quot;0&amp;quot;: _(u&amp;quot;Tornado&amp;quot;),
        &amp;quot;1&amp;quot;: _(u&amp;quot;Tropical Storm&amp;quot;),
        &amp;quot;2&amp;quot;: _(u&amp;quot;Hurricane&amp;quot;),
        &amp;quot;3&amp;quot;: _(u&amp;quot;Severe Thunderstorms&amp;quot;),
        &amp;quot;4&amp;quot;: _(u&amp;quot;Thunderstorms&amp;quot;),
        &amp;quot;5&amp;quot;: _(u&amp;quot;Mixed Rain and Snow&amp;quot;),
        &amp;quot;6&amp;quot;: _(u&amp;quot;Mixed Rain and Sleet&amp;quot;),
        &amp;quot;7&amp;quot;: _(u&amp;quot;Mixed Precipitation&amp;quot;),
        &amp;quot;8&amp;quot;: _(u&amp;quot;Freezing Drizzle&amp;quot;),
        &amp;quot;9&amp;quot;: _(u&amp;quot;Drizzle&amp;quot;),
        &amp;quot;10&amp;quot;: _(u&amp;quot;Freezing Rain&amp;quot;),
        &amp;quot;11&amp;quot;: _(u&amp;quot;Showers&amp;quot;),
        &amp;quot;12&amp;quot;: _(u&amp;quot;Showers&amp;quot;),
        &amp;quot;13&amp;quot;: _(u&amp;quot;Snow Flurries&amp;quot;),
        &amp;quot;14&amp;quot;: _(u&amp;quot;Light Snow Showers&amp;quot;),
        &amp;quot;15&amp;quot;: _(u&amp;quot;Blowing Snow&amp;quot;),
        &amp;quot;16&amp;quot;: _(u&amp;quot;Snow&amp;quot;),
        &amp;quot;17&amp;quot;: _(u&amp;quot;Hail&amp;quot;),
        &amp;quot;18&amp;quot;: _(u&amp;quot;Sleet&amp;quot;),
        &amp;quot;19&amp;quot;: _(u&amp;quot;Dust&amp;quot;),
        &amp;quot;20&amp;quot;: _(u&amp;quot;Fog&amp;quot;),
        &amp;quot;21&amp;quot;: _(u&amp;quot;Haze&amp;quot;),
        &amp;quot;22&amp;quot;: _(u&amp;quot;Smoke&amp;quot;),
        &amp;quot;23&amp;quot;: _(u&amp;quot;Blustery&amp;quot;),
        &amp;quot;24&amp;quot;: _(u&amp;quot;Windy&amp;quot;),
        &amp;quot;25&amp;quot;: _(u&amp;quot;Cold&amp;quot;),
        &amp;quot;26&amp;quot;: _(u&amp;quot;Cloudy&amp;quot;),
        &amp;quot;27&amp;quot;: _(u&amp;quot;Mostly Cloudy&amp;quot;),
        &amp;quot;28&amp;quot;: _(u&amp;quot;Mostly Cloudy&amp;quot;),
        &amp;quot;29&amp;quot;: _(u&amp;quot;Partly Cloudy&amp;quot;),
        &amp;quot;30&amp;quot;: _(u&amp;quot;Partly Cloudy&amp;quot;),
        &amp;quot;31&amp;quot;: _(u&amp;quot;Clear&amp;quot;),
        &amp;quot;32&amp;quot;: _(u&amp;quot;Clear&amp;quot;),
        &amp;quot;33&amp;quot;: _(u&amp;quot;Fair&amp;quot;),
        &amp;quot;34&amp;quot;: _(u&amp;quot;Fair&amp;quot;),
        &amp;quot;35&amp;quot;: _(u&amp;quot;Mixed Rain and Hail&amp;quot;),
        &amp;quot;36&amp;quot;: _(u&amp;quot;Hot&amp;quot;),
        &amp;quot;37&amp;quot;: _(u&amp;quot;Isolated Thunderstorms&amp;quot;),
        &amp;quot;38&amp;quot;: _(u&amp;quot;Scattered Thunderstorms&amp;quot;),
        &amp;quot;39&amp;quot;: _(u&amp;quot;Scattered Thunderstorms&amp;quot;),
        &amp;quot;40&amp;quot;: _(u&amp;quot;Scattered Showers&amp;quot;),
        &amp;quot;41&amp;quot;: _(u&amp;quot;Heavy Snow&amp;quot;),
        &amp;quot;42&amp;quot;: _(u&amp;quot;Scattered Snow Showers&amp;quot;),
        &amp;quot;43&amp;quot;: _(u&amp;quot;Heavy Snow&amp;quot;),
        &amp;quot;44&amp;quot;: _(u&amp;quot;Partly Cloudy&amp;quot;),
        &amp;quot;45&amp;quot;: _(u&amp;quot;Thunder Showers&amp;quot;),
        &amp;quot;46&amp;quot;: _(u&amp;quot;Snow Showers&amp;quot;),
        &amp;quot;47&amp;quot;: _(u&amp;quot;Isolated Thunderstorms&amp;quot;),
        &amp;quot;na&amp;quot;: _(u&amp;quot;N/A&amp;quot;),
        &amp;quot;-&amp;quot;: _(u&amp;quot;N/A&amp;quot;)
    }

    conditions_text_es = {
        &amp;quot;0&amp;quot;: _(u&amp;quot;Tornado&amp;quot;),
        &amp;quot;1&amp;quot;: _(u&amp;quot;Tormenta Tropical&amp;quot;),
        &amp;quot;2&amp;quot;: _(u&amp;quot;Huracá¡n&amp;quot;),
        &amp;quot;3&amp;quot;: _(u&amp;quot;Tormentas Fuertes&amp;quot;),
        &amp;quot;4&amp;quot;: _(u&amp;quot;Tormentas&amp;quot;),
        &amp;quot;5&amp;quot;: _(u&amp;quot;Lluvia y Nieve Mezclada&amp;quot;),
        &amp;quot;6&amp;quot;: _(u&amp;quot;Lluvia y Aguanieve Mezclada&amp;quot;),
        &amp;quot;7&amp;quot;: _(u&amp;quot;Aguanieve&amp;quot;),
        &amp;quot;8&amp;quot;: _(u&amp;quot;Llovizna Helada&amp;quot;),
        &amp;quot;9&amp;quot;: _(u&amp;quot;Llovizna&amp;quot;),
        &amp;quot;10&amp;quot;: _(u&amp;quot;Lluvia Engelante&amp;quot;), # o lluvia helada
        &amp;quot;11&amp;quot;: _(u&amp;quot;Chaparrones&amp;quot;),
        &amp;quot;12&amp;quot;: _(u&amp;quot;Chaparrones&amp;quot;),
        &amp;quot;13&amp;quot;: _(u&amp;quot;Nieve Ligera&amp;quot;),
        &amp;quot;14&amp;quot;: _(u&amp;quot;Nieve Ligera&amp;quot;),
        &amp;quot;15&amp;quot;: _(u&amp;quot;Ventisca de Nieve&amp;quot;),
        &amp;quot;16&amp;quot;: _(u&amp;quot;Nieve&amp;quot;),
        &amp;quot;17&amp;quot;: _(u&amp;quot;Granizo&amp;quot;),
        &amp;quot;18&amp;quot;: _(u&amp;quot;Aguanieve&amp;quot;),
        &amp;quot;19&amp;quot;: _(u&amp;quot;Polvo&amp;quot;),
        &amp;quot;20&amp;quot;: _(u&amp;quot;Niebla&amp;quot;),
        &amp;quot;21&amp;quot;: _(u&amp;quot;Bruma&amp;quot;),
        &amp;quot;22&amp;quot;: _(u&amp;quot;Humo&amp;quot;),
        &amp;quot;23&amp;quot;: _(u&amp;quot;Tempestad&amp;quot;),
        &amp;quot;24&amp;quot;: _(u&amp;quot;Ventoso&amp;quot;),
        &amp;quot;25&amp;quot;: _(u&amp;quot;Fráo&amp;quot;),
        &amp;quot;26&amp;quot;: _(u&amp;quot;Muy Nublado&amp;quot;),
        &amp;quot;27&amp;quot;: _(u&amp;quot;Principalmente Nublado&amp;quot;),
        &amp;quot;28&amp;quot;: _(u&amp;quot;Principalmente Nublado&amp;quot;),
        &amp;quot;29&amp;quot;: _(u&amp;quot;Parcialmente Nublado&amp;quot;),
        &amp;quot;30&amp;quot;: _(u&amp;quot;Parcialmente Nublado&amp;quot;),
        &amp;quot;31&amp;quot;: _(u&amp;quot;Despejado&amp;quot;),
        &amp;quot;32&amp;quot;: _(u&amp;quot;Despejado&amp;quot;),
        &amp;quot;33&amp;quot;: _(u&amp;quot;Algo Nublado&amp;quot;),
        &amp;quot;34&amp;quot;: _(u&amp;quot;Algo Nublado&amp;quot;),
        &amp;quot;35&amp;quot;: _(u&amp;quot;Lluvia con Granizo&amp;quot;),
        &amp;quot;36&amp;quot;: _(u&amp;quot;Calor&amp;quot;),
        &amp;quot;37&amp;quot;: _(u&amp;quot;Tormentas Aisladas&amp;quot;),
        &amp;quot;38&amp;quot;: _(u&amp;quot;Tormentas Dispersas&amp;quot;),
        &amp;quot;39&amp;quot;: _(u&amp;quot;Tormentas Dispersas&amp;quot;),
        &amp;quot;40&amp;quot;: _(u&amp;quot;Chubascos Dispersos&amp;quot;),
        &amp;quot;41&amp;quot;: _(u&amp;quot;Nieve Pesada&amp;quot;),
        &amp;quot;42&amp;quot;: _(u&amp;quot;Nevadas Débiles y Dispersas&amp;quot;),
        &amp;quot;43&amp;quot;: _(u&amp;quot;Nevada Intensa&amp;quot;),
        &amp;quot;44&amp;quot;: _(u&amp;quot;Nubes Dispersas&amp;quot;),
        &amp;quot;45&amp;quot;: _(u&amp;quot;Tormentas&amp;quot;),
        &amp;quot;46&amp;quot;: _(u&amp;quot;Nevadas Dispersas&amp;quot;),
        &amp;quot;47&amp;quot;: _(u&amp;quot;Tormentas Aisladas&amp;quot;),
        &amp;quot;na&amp;quot;: _(u&amp;quot;N/A&amp;quot;),
        &amp;quot;-&amp;quot;: _(u&amp;quot;N/A&amp;quot;)
    }

    conditions_text_fr = {
        &amp;quot;0&amp;quot;: _(u&amp;quot;Tornade&amp;quot;),
        &amp;quot;1&amp;quot;: _(u&amp;quot;Tempête Tropicale&amp;quot;),
        &amp;quot;2&amp;quot;: _(u&amp;quot;Ouragan&amp;quot;),
        &amp;quot;3&amp;quot;: _(u&amp;quot;Orages Violents&amp;quot;),
        &amp;quot;4&amp;quot;: _(u&amp;quot;Orageux&amp;quot;),
        &amp;quot;5&amp;quot;: _(u&amp;quot;Pluie et Neige&amp;quot;),
        &amp;quot;6&amp;quot;: _(u&amp;quot;Pluie et Neige Mouillée&amp;quot;),
        &amp;quot;7&amp;quot;: _(u&amp;quot;Variable avec averses&amp;quot;),
        &amp;quot;8&amp;quot;: _(u&amp;quot;Bruine Givrante&amp;quot;),
        &amp;quot;9&amp;quot;: _(u&amp;quot;Bruine&amp;quot;),
        &amp;quot;10&amp;quot;: _(u&amp;quot;Pluie Glacante&amp;quot;),
        &amp;quot;11&amp;quot;: _(u&amp;quot;Averses&amp;quot;),
        &amp;quot;12&amp;quot;: _(u&amp;quot;Averses&amp;quot;),
        &amp;quot;13&amp;quot;: _(u&amp;quot;Légère Neige&amp;quot;),
        &amp;quot;14&amp;quot;: _(u&amp;quot;Forte Neige&amp;quot;),
        &amp;quot;15&amp;quot;: _(u&amp;quot;Tempête de Neige&amp;quot;),
        &amp;quot;16&amp;quot;: _(u&amp;quot;Neige&amp;quot;),
        &amp;quot;17&amp;quot;: _(u&amp;quot;Grêle&amp;quot;),
        &amp;quot;18&amp;quot;: _(u&amp;quot;Pluie/Neige&amp;quot;),
        &amp;quot;19&amp;quot;: _(u&amp;quot;Nuage de poussière&amp;quot;),
        &amp;quot;20&amp;quot;: _(u&amp;quot;Brouillard&amp;quot;),
        &amp;quot;21&amp;quot;: _(u&amp;quot;Brume&amp;quot;),
        &amp;quot;22&amp;quot;: _(u&amp;quot;Fumée&amp;quot;),
        &amp;quot;23&amp;quot;: _(u&amp;quot;Tres Venteux&amp;quot;),
        &amp;quot;24&amp;quot;: _(u&amp;quot;Venteux&amp;quot;),
        &amp;quot;25&amp;quot;: _(u&amp;quot;Froid&amp;quot;),
        &amp;quot;26&amp;quot;: _(u&amp;quot;Nuageux&amp;quot;),
        &amp;quot;27&amp;quot;: _(u&amp;quot;Tres Nuageux&amp;quot;),
        &amp;quot;28&amp;quot;: _(u&amp;quot;Tres Nuageux&amp;quot;),
        &amp;quot;29&amp;quot;: _(u&amp;quot;Nuages Disséminés&amp;quot;),
        &amp;quot;30&amp;quot;: _(u&amp;quot;Nuages Disséminés&amp;quot;),
        &amp;quot;31&amp;quot;: _(u&amp;quot;Beau&amp;quot;),
        &amp;quot;32&amp;quot;: _(u&amp;quot;Beau&amp;quot;),
        &amp;quot;33&amp;quot;: _(u&amp;quot;Belles Éclaircies&amp;quot;),
        &amp;quot;34&amp;quot;: _(u&amp;quot;Belles Éclaircies&amp;quot;),
        &amp;quot;35&amp;quot;: _(u&amp;quot;Pluie avec Grêle&amp;quot;),
        &amp;quot;36&amp;quot;: _(u&amp;quot;Chaleur&amp;quot;),
        &amp;quot;37&amp;quot;: _(u&amp;quot;Orages Isolés&amp;quot;),
        &amp;quot;38&amp;quot;: _(u&amp;quot;Orages Localisés&amp;quot;),
        &amp;quot;39&amp;quot;: _(u&amp;quot;Orages Localisés&amp;quot;),
        &amp;quot;40&amp;quot;: _(u&amp;quot;Averses Localisées&amp;quot;),
        &amp;quot;41&amp;quot;: _(u&amp;quot;Neige Lourde&amp;quot;),
        &amp;quot;42&amp;quot;: _(u&amp;quot;Tempête de Neige Localisées&amp;quot;),
        &amp;quot;43&amp;quot;: _(u&amp;quot;Neige Lourde&amp;quot;),
        &amp;quot;44&amp;quot;: _(u&amp;quot;Nuages Disséminés&amp;quot;),
        &amp;quot;45&amp;quot;: _(u&amp;quot;Orages&amp;quot;),
        &amp;quot;46&amp;quot;: _(u&amp;quot;Tempête de Neige&amp;quot;),
        &amp;quot;47&amp;quot;: _(u&amp;quot;Orages Isolés&amp;quot;),
        &amp;quot;na&amp;quot;: _(u&amp;quot;N/A&amp;quot;),
        &amp;quot;-&amp;quot;: _(u&amp;quot;N/A&amp;quot;)
    }

    conditions_weather_font = {
        &amp;quot;0&amp;quot;: _(u&amp;quot;W&amp;quot;),
        &amp;quot;1&amp;quot;: _(u&amp;quot;V&amp;quot;),
        &amp;quot;2&amp;quot;: _(u&amp;quot;W&amp;quot;),
        &amp;quot;3&amp;quot;: _(u&amp;quot;s&amp;quot;),
        &amp;quot;4&amp;quot;: _(u&amp;quot;p&amp;quot;),
        &amp;quot;5&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;6&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;7&amp;quot;: _(u&amp;quot;g&amp;quot;),
        &amp;quot;8&amp;quot;: _(u&amp;quot;g&amp;quot;),
        &amp;quot;9&amp;quot;: _(u&amp;quot;g&amp;quot;),
        &amp;quot;10&amp;quot;: _(u&amp;quot;h&amp;quot;),
        &amp;quot;11&amp;quot;: _(u&amp;quot;g&amp;quot;),
        &amp;quot;12&amp;quot;: _(u&amp;quot;g&amp;quot;),
        &amp;quot;13&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;14&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;15&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;16&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;17&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;18&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;19&amp;quot;: _(u&amp;quot;e&amp;quot;),
        &amp;quot;20&amp;quot;: _(u&amp;quot;e&amp;quot;),
        &amp;quot;21&amp;quot;: _(u&amp;quot;a&amp;quot;),
        &amp;quot;22&amp;quot;: _(u&amp;quot;d&amp;quot;),
        &amp;quot;23&amp;quot;: _(u&amp;quot;d&amp;quot;),
        &amp;quot;24&amp;quot;: _(u&amp;quot;d&amp;quot;),
        &amp;quot;25&amp;quot;: _(u&amp;quot;d&amp;quot;),
        &amp;quot;26&amp;quot;: _(u&amp;quot;e&amp;quot;),
        &amp;quot;27&amp;quot;: _(u&amp;quot;e&amp;quot;),
        &amp;quot;28&amp;quot;: _(u&amp;quot;e&amp;quot;),
        &amp;quot;29&amp;quot;: _(u&amp;quot;c&amp;quot;),
        &amp;quot;30&amp;quot;: _(u&amp;quot;c&amp;quot;),
        &amp;quot;31&amp;quot;: _(u&amp;quot;a&amp;quot;),
        &amp;quot;32&amp;quot;: _(u&amp;quot;a&amp;quot;),
        &amp;quot;33&amp;quot;: _(u&amp;quot;b&amp;quot;),
        &amp;quot;34&amp;quot;: _(u&amp;quot;b&amp;quot;),
        &amp;quot;35&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;36&amp;quot;: _(u&amp;quot;a&amp;quot;),
        &amp;quot;37&amp;quot;: _(u&amp;quot;f&amp;quot;),
        &amp;quot;38&amp;quot;: _(u&amp;quot;f&amp;quot;),
        &amp;quot;39&amp;quot;: _(u&amp;quot;f&amp;quot;),
        &amp;quot;40&amp;quot;: _(u&amp;quot;g&amp;quot;),
        &amp;quot;41&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;42&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;43&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;44&amp;quot;: _(u&amp;quot;b&amp;quot;),
        &amp;quot;45&amp;quot;: _(u&amp;quot;g&amp;quot;),
        &amp;quot;46&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;47&amp;quot;: _(u&amp;quot;f&amp;quot;),
        &amp;quot;na&amp;quot;: _(u&amp;quot;&amp;quot;),
        &amp;quot;-&amp;quot;: _(u&amp;quot;&amp;quot;)
    }

    conditions_moon_font = {
        &amp;quot;0&amp;quot;: _(u&amp;quot;0&amp;quot;),
        &amp;quot;1&amp;quot;: _(u&amp;quot;1&amp;quot;),
        &amp;quot;2&amp;quot;: _(u&amp;quot;2&amp;quot;),
        &amp;quot;3&amp;quot;: _(u&amp;quot;3&amp;quot;),
        &amp;quot;4&amp;quot;: _(u&amp;quot;4&amp;quot;),
        &amp;quot;5&amp;quot;: _(u&amp;quot;5&amp;quot;),
        &amp;quot;6&amp;quot;: _(u&amp;quot;6&amp;quot;),
        &amp;quot;7&amp;quot;: _(u&amp;quot;7&amp;quot;),
        &amp;quot;8&amp;quot;: _(u&amp;quot;8&amp;quot;),
        &amp;quot;9&amp;quot;: _(u&amp;quot;9&amp;quot;),
        &amp;quot;10&amp;quot;: _(u&amp;quot;10&amp;quot;),
        &amp;quot;11&amp;quot;: _(u&amp;quot;11&amp;quot;),
        &amp;quot;12&amp;quot;: _(u&amp;quot;12&amp;quot;),
        &amp;quot;13&amp;quot;: _(u&amp;quot;13&amp;quot;),
        &amp;quot;14&amp;quot;: _(u&amp;quot;14&amp;quot;),
        &amp;quot;15&amp;quot;: _(u&amp;quot;15&amp;quot;),
        &amp;quot;16&amp;quot;: _(u&amp;quot;16&amp;quot;),
        &amp;quot;17&amp;quot;: _(u&amp;quot;17&amp;quot;),
        &amp;quot;18&amp;quot;: _(u&amp;quot;18&amp;quot;),
        &amp;quot;19&amp;quot;: _(u&amp;quot;19&amp;quot;),
        &amp;quot;20&amp;quot;: _(u&amp;quot;20&amp;quot;),
        &amp;quot;21&amp;quot;: _(u&amp;quot;21&amp;quot;),
        &amp;quot;22&amp;quot;: _(u&amp;quot;22&amp;quot;),
        &amp;quot;23&amp;quot;: _(u&amp;quot;23&amp;quot;),
        &amp;quot;24&amp;quot;: _(u&amp;quot;24&amp;quot;),
        &amp;quot;25&amp;quot;: _(u&amp;quot;25&amp;quot;),
        &amp;quot;na&amp;quot;: _(u&amp;quot;&amp;quot;),
        &amp;quot;-&amp;quot;: _(u&amp;quot;&amp;quot;)
    }

    day_of_week = {
        &amp;quot;Today&amp;quot;: _(u&amp;quot;Today&amp;quot;),
        &amp;quot;Monday&amp;quot;: _(u&amp;quot;Monday&amp;quot;),
        &amp;quot;Tuesday&amp;quot;: _(u&amp;quot;Tuesday&amp;quot;),
        &amp;quot;Wednesday&amp;quot;: _(u&amp;quot;Wednesday&amp;quot;),
        &amp;quot;Thursday&amp;quot;: _(u&amp;quot;Thursday&amp;quot;),
        &amp;quot;Friday&amp;quot;: _(u&amp;quot;Friday&amp;quot;),
        &amp;quot;Saturday&amp;quot;: _(u&amp;quot;Saturday&amp;quot;),
        &amp;quot;Sunday&amp;quot;: _(u&amp;quot;Sunday&amp;quot;)
    }

    day_of_week_short = {
        &amp;quot;Today&amp;quot;: _(u&amp;quot;Now&amp;quot;),
        &amp;quot;Monday&amp;quot;: _(u&amp;quot;Mon&amp;quot;),
        &amp;quot;Tuesday&amp;quot;: _(u&amp;quot;Tue&amp;quot;),
        &amp;quot;Wednesday&amp;quot;: _(u&amp;quot;Wed&amp;quot;),
        &amp;quot;Thursday&amp;quot;: _(u&amp;quot;Thu&amp;quot;),
        &amp;quot;Friday&amp;quot;: _(u&amp;quot;Fri&amp;quot;),
        &amp;quot;Saturday&amp;quot;: _(u&amp;quot;Sat&amp;quot;),
        &amp;quot;Sunday&amp;quot;: _(u&amp;quot;Sun&amp;quot;)
    }

    day_of_week_es = {
        &amp;quot;Today&amp;quot;: _(u&amp;quot;hoy&amp;quot;),
        &amp;quot;Monday&amp;quot;: _(u&amp;quot;lunes&amp;quot;),
        &amp;quot;Tuesday&amp;quot;: _(u&amp;quot;martes&amp;quot;),
        &amp;quot;Wednesday&amp;quot;: _(u&amp;quot;miércoles&amp;quot;),
        &amp;quot;Thursday&amp;quot;: _(u&amp;quot;jueves&amp;quot;),
        &amp;quot;Friday&amp;quot;: _(u&amp;quot;viernes&amp;quot;),
        &amp;quot;Saturday&amp;quot;: _(u&amp;quot;sábado&amp;quot;),
        &amp;quot;Sunday&amp;quot;: _(u&amp;quot;domingo&amp;quot;)
    }

    day_of_week_short_es = {
        &amp;quot;Today&amp;quot;: _(u&amp;quot;hoy&amp;quot;),
        &amp;quot;Monday&amp;quot;: _(u&amp;quot;lun&amp;quot;),
        &amp;quot;Tuesday&amp;quot;: _(u&amp;quot;mar&amp;quot;),
        &amp;quot;Wednesday&amp;quot;: _(u&amp;quot;mié&amp;quot;),
        &amp;quot;Thursday&amp;quot;: _(u&amp;quot;jue&amp;quot;),
        &amp;quot;Friday&amp;quot;: _(u&amp;quot;vie&amp;quot;),
        &amp;quot;Saturday&amp;quot;: _(u&amp;quot;sáb&amp;quot;),
        &amp;quot;Sunday&amp;quot;: _(u&amp;quot;dom&amp;quot;)
    }

    day_of_week_fr = {
        &amp;quot;Today&amp;quot;: _(u&amp;quot;Aujourd'hui&amp;quot;),
        &amp;quot;Monday&amp;quot;: _(u&amp;quot;Lundi&amp;quot;),
        &amp;quot;Tuesday&amp;quot;: _(u&amp;quot;Mardi&amp;quot;),
        &amp;quot;Wednesday&amp;quot;: _(u&amp;quot;Mercredi&amp;quot;),
        &amp;quot;Thursday&amp;quot;: _(u&amp;quot;Jeudi&amp;quot;),
        &amp;quot;Friday&amp;quot;: _(u&amp;quot;Vendredi&amp;quot;),
        &amp;quot;Saturday&amp;quot;: _(u&amp;quot;Samedi&amp;quot;),
        &amp;quot;Sunday&amp;quot;: _(u&amp;quot;Dimanche&amp;quot;)
    }

    day_of_week_short_fr = {
        &amp;quot;Today&amp;quot;: _(u&amp;quot;Auj&amp;quot;),
        &amp;quot;Monday&amp;quot;: _(u&amp;quot;Lun&amp;quot;),
        &amp;quot;Tuesday&amp;quot;: _(u&amp;quot;Mar&amp;quot;),
        &amp;quot;Wednesday&amp;quot;: _(u&amp;quot;Mer&amp;quot;),
        &amp;quot;Thursday&amp;quot;: _(u&amp;quot;Jeu&amp;quot;),
        &amp;quot;Friday&amp;quot;: _(u&amp;quot;Ven&amp;quot;),
        &amp;quot;Saturday&amp;quot;: _(u&amp;quot;Sam&amp;quot;),
        &amp;quot;Sunday&amp;quot;: _(u&amp;quot;Dim&amp;quot;)
    }

    bearing_arrow_font = {
        &amp;quot;N&amp;quot;: _(u&amp;quot;i&amp;quot;),
        &amp;quot;NNE&amp;quot;: _(u&amp;quot;j&amp;quot;),
        &amp;quot;NE&amp;quot;: _(u&amp;quot;k&amp;quot;),
        &amp;quot;ENE&amp;quot;: _(u&amp;quot;l&amp;quot;),
        &amp;quot;E&amp;quot;: _(u&amp;quot;m&amp;quot;),
        &amp;quot;ESE&amp;quot;: _(u&amp;quot;n&amp;quot;),
        &amp;quot;SE&amp;quot;: _(u&amp;quot;o&amp;quot;),
        &amp;quot;SSE&amp;quot;: _(u&amp;quot;p&amp;quot;),
        &amp;quot;S&amp;quot;: _(u&amp;quot;a&amp;quot;),
        &amp;quot;SSW&amp;quot;: _(u&amp;quot;b&amp;quot;),
        &amp;quot;SW&amp;quot;: _(u&amp;quot;c&amp;quot;),
        &amp;quot;WSW&amp;quot;: _(u&amp;quot;d&amp;quot;),
        &amp;quot;W&amp;quot;: _(u&amp;quot;e&amp;quot;),
        &amp;quot;WNW&amp;quot;: _(u&amp;quot;f&amp;quot;),
        &amp;quot;NW&amp;quot;: _(u&amp;quot;g&amp;quot;),
        &amp;quot;NNW&amp;quot;: _(u&amp;quot;h&amp;quot;),
        &amp;quot;N/A&amp;quot;: _(u&amp;quot; &amp;quot;)
    }

    bearing_text_es = {
        &amp;quot;N&amp;quot;: _(u&amp;quot;N&amp;quot;),
        &amp;quot;NNE&amp;quot;: _(u&amp;quot;NNE&amp;quot;),
        &amp;quot;NE&amp;quot;: _(u&amp;quot;NE&amp;quot;),
        &amp;quot;ENE&amp;quot;: _(u&amp;quot;ENE&amp;quot;),
        &amp;quot;E&amp;quot;: _(u&amp;quot;E&amp;quot;),
        &amp;quot;ESE&amp;quot;: _(u&amp;quot;ESE&amp;quot;),
        &amp;quot;SE&amp;quot;: _(u&amp;quot;SE&amp;quot;),
        &amp;quot;SSE&amp;quot;: _(u&amp;quot;SSE&amp;quot;),
        &amp;quot;S&amp;quot;: _(u&amp;quot;S&amp;quot;),
        &amp;quot;SSW&amp;quot;: _(u&amp;quot;SSO&amp;quot;),
        &amp;quot;SW&amp;quot;: _(u&amp;quot;SO&amp;quot;),
        &amp;quot;WSW&amp;quot;: _(u&amp;quot;WOW&amp;quot;),
        &amp;quot;W&amp;quot;: _(u&amp;quot;O&amp;quot;),
        &amp;quot;WNW&amp;quot;: _(u&amp;quot;ONO&amp;quot;),
        &amp;quot;NW&amp;quot;: _(u&amp;quot;NO&amp;quot;),
        &amp;quot;NNW&amp;quot;: _(u&amp;quot;NNO&amp;quot;),
        &amp;quot;N/A&amp;quot;: _(u&amp;quot;N\A&amp;quot;)
    }

    bearing_text_fr = {
        &amp;quot;N&amp;quot;: _(u&amp;quot;N&amp;quot;),
        &amp;quot;NNE&amp;quot;: _(u&amp;quot;NNE&amp;quot;),
        &amp;quot;NE&amp;quot;: _(u&amp;quot;NE&amp;quot;),
        &amp;quot;ENE&amp;quot;: _(u&amp;quot;ENE&amp;quot;),
        &amp;quot;E&amp;quot;: _(u&amp;quot;E&amp;quot;),
        &amp;quot;ESE&amp;quot;: _(u&amp;quot;ESE&amp;quot;),
        &amp;quot;SE&amp;quot;: _(u&amp;quot;SE&amp;quot;),
        &amp;quot;SSE&amp;quot;: _(u&amp;quot;SSE&amp;quot;),
        &amp;quot;S&amp;quot;: _(u&amp;quot;S&amp;quot;),
        &amp;quot;SSW&amp;quot;: _(u&amp;quot;SSO&amp;quot;),
        &amp;quot;SW&amp;quot;: _(u&amp;quot;SO&amp;quot;),
        &amp;quot;WSW&amp;quot;: _(u&amp;quot;WOW&amp;quot;),
        &amp;quot;W&amp;quot;: _(u&amp;quot;O&amp;quot;),
        &amp;quot;WNW&amp;quot;: _(u&amp;quot;ONO&amp;quot;),
        &amp;quot;NW&amp;quot;: _(u&amp;quot;NO&amp;quot;),
        &amp;quot;NNW&amp;quot;: _(u&amp;quot;NNO&amp;quot;),
        &amp;quot;N/A&amp;quot;: _(u&amp;quot;N\A&amp;quot;)
    }

class GlobalWeather:

    current_conditions = []
    day_forecast = []
    night_forecast = []

    locale = &amp;quot;en&amp;quot;

    options = None
    weatherxmldoc = &amp;quot;&amp;quot;

        TEMP_FILEPATH_CURRENT = &amp;quot;/tmp/conkyForecast-c-LOCATION.pkl&amp;quot;
    TEMP_FILEPATH_DAYFORECAST = &amp;quot;/tmp/conkyForecast-df-LOCATION.pkl&amp;quot;
    TEMP_FILEPATH_NIGHTFORECAST = &amp;quot;/tmp/conkyForecast-nf-LOCATION.pkl&amp;quot;
        EXPIRY_MINUTES = 30
    DEFAULT_SPACING = u&amp;quot; &amp;quot;


    def __init__(self,options):

        self.options = options

        if self.options.locale == None:
            try:
                self.locale = locale.getdefaultlocale()[0][0:2]
                #self.locale = &amp;quot;es&amp;quot; #uncomment this line to force Spanish locale
                #self.locale = &amp;quot;fr&amp;quot; #uncomment this line to force French locale
            except:
                print &amp;quot;locale not set&amp;quot;
        else:
            self.locale = self.options.locale
            #self.locale = &amp;quot;es&amp;quot; #uncomment this line to force Spanish locale
            #self.locale = &amp;quot;fr&amp;quot; #uncomment this line to force French locale

        if self.options.verbose == True:
            print &amp;gt;&amp;gt; sys.stdout, &amp;quot;locale set to &amp;quot;,self.locale

    def getText(self,nodelist):
        rc = &amp;quot;&amp;quot;
        for node in nodelist:
            if node.nodeType == node.TEXT_NODE:
                rc = rc + node.data
        return rc


    def getSpaces(self,spaces):
        string = u&amp;quot;&amp;quot;
        if spaces == None:
            string = self.DEFAULT_SPACING
        else:
            for i in range(0, spaces+1):
                string = string + u&amp;quot; &amp;quot;
        return string


    def isNumeric(self,string):
        try:
            dummy = float(string)
            return True
        except:
            return False

    def isConnectionAvailable(self):
        # ensure we can access weather.com's server by opening the url
        try:
            usock = urllib2.urlopen('http://xoap.weather.com')
            usock.close()
            return True
        except:
            return False

    def getBearingText(self,bearing):
        bearing = float(bearing)
        if bearing  11.25:
            return u&amp;quot;N&amp;quot;
        elif bearing  33.75:
            return u&amp;quot;NNE&amp;quot;
        elif bearing  56.25:
            return u&amp;quot;NE&amp;quot;
        elif bearing  78.75:
            return u&amp;quot;ENE&amp;quot;
        elif bearing  101.25:
            return u&amp;quot;E&amp;quot;
        elif bearing  123.75:
            return u&amp;quot;ESE&amp;quot;
        elif bearing  146.25:
            return u&amp;quot;SE&amp;quot;
        elif bearing  168.75:
            return u&amp;quot;SSE&amp;quot;
        elif bearing  191.25:
            return u&amp;quot;S&amp;quot;
        elif bearing  213.75:
            return u&amp;quot;SSW&amp;quot;
        elif bearing  236.25:
            return u&amp;quot;SW&amp;quot;
        elif bearing  258.75:
            return u&amp;quot;WSW&amp;quot;
        elif bearing  281.25:
            return u&amp;quot;W&amp;quot;
        elif bearing  303.75:
            return u&amp;quot;WNW&amp;quot;
        elif bearing  326.25:
            return u&amp;quot;NW&amp;quot;
        elif bearing  348.75:
            return u&amp;quot;NNW&amp;quot;
        else:
            return &amp;quot;N/A&amp;quot;

    def convertCelsiusToFahrenheit(self,temp):
        return str(int(floor(((float(temp)*9.0)/5.0)+32)))

    def convertKilometresToMiles(self,dist):
        return str(int(floor(float(dist)*0.621371192)))

    def convertMillibarsToInches(self,mb):
        return str(int(floor(float(mb)/33.8582)))

    def getTemplateList(self,template):

    templatelist = []

    for template_part in template.split(&amp;quot;{&amp;quot;):
            if template_part != &amp;quot;&amp;quot;:
                for template_part in template_part.split(&amp;quot;}&amp;quot;):
                    if template_part != &amp;quot;&amp;quot;:
                        templatelist.append(u&amp;quot;&amp;quot;+template_part)

        return templatelist


    def getOutputText(self,datatype,startday,endday,night,shortweekday,imperial,hideunits,spaces):
        #try:
            output = u&amp;quot;&amp;quot;

            # define current units for output
            if hideunits == False:
                if imperial == False:
                    tempunit = u&amp;quot;°C&amp;quot;
                    speedunit = u&amp;quot;kph&amp;quot;
                    pressureunit = u&amp;quot;mb&amp;quot;
                else:
                    tempunit = u&amp;quot;°F&amp;quot;
                    speedunit = u&amp;quot;mph&amp;quot;
                    pressureunit = u&amp;quot;in&amp;quot;
            else:
                tempunit = u&amp;quot;°&amp;quot;
                speedunit = u&amp;quot;&amp;quot;
                pressureunit = u&amp;quot;&amp;quot;

            if startday == None: # current conditions

                if datatype == &amp;quot;DW&amp;quot;:
                    if self.locale == &amp;quot;es&amp;quot;:
                        if shortweekday == True:
                            output = WeatherText.day_of_week_short_es[self.current_conditions[0].day_of_week]
                        else:
                            output = WeatherText.day_of_week_es[self.current_conditions[0].day_of_week]
                    elif self.locale == &amp;quot;fr&amp;quot;:
                        if shortweekday == True:
                            output = WeatherText.day_of_week_short_fr[self.current_conditions[0].day_of_week]
                        else:
                            output = WeatherText.day_of_week_fr[self.current_conditions[0].day_of_week]
                    else:
                        if shortweekday == True:
                            output = WeatherText.day_of_week_short[self.current_conditions[0].day_of_week]
                        else:
                            output = WeatherText.day_of_week[self.current_conditions[0].day_of_week]
                elif datatype == &amp;quot;WF&amp;quot;: # weather font
                    output = WeatherText.conditions_weather_font[self.current_conditions[0].condition_code]
                elif datatype == &amp;quot;LT&amp;quot;:
                    string = self.current_conditions[0].low
                    if self.isNumeric(string) == True:
                        if imperial == True:
                            string = self.convertCelsiusToFahrenheit(string)
                        string = string + tempunit
                    output = string
                elif datatype == &amp;quot;HT&amp;quot;:
                    string = self.current_conditions[0].high
                    if self.isNumeric(string) == True:
                        if imperial == True:
                            string = self.convertCelsiusToFahrenheit(string)
                        string = string + tempunit
                    output = string
                elif datatype == &amp;quot;CC&amp;quot;:
                    if self.locale == &amp;quot;es&amp;quot;:
                        output = WeatherText.conditions_text_es[self.current_conditions[0].condition_code]
                    elif self.locale == &amp;quot;fr&amp;quot;:
                        output = WeatherText.conditions_text_fr[self.current_conditions[0].condition_code]
                    else:
                        output = WeatherText.conditions_text[self.current_conditions[0].condition_code]
                elif datatype == &amp;quot;CT&amp;quot;:
                    output = self.current_conditions[0].condition_text
                elif datatype == &amp;quot;PC&amp;quot;:
                    string = self.current_conditions[0].precip
                    if self.isNumeric(string) == True:
                        string = string + u&amp;quot;%&amp;quot;
                    output = string
                elif datatype == &amp;quot;HM&amp;quot;:
                    string = self.current_conditions[0].humidity
                    if self.isNumeric(string) == True:
                        string = string + u&amp;quot;%&amp;quot;
                    output = string
                elif datatype == &amp;quot;WD&amp;quot;:
                    string = self.current_conditions[0].wind_dir
                    if self.isNumeric(string) == True:
                        string = self.getBearingText(string)

                    if self.locale == &amp;quot;es&amp;quot;:
                        output = WeatherText.bearing_text_es[string]
                    elif self.locale == &amp;quot;fr&amp;quot;:
                        output = WeatherText.bearing_text_fr[string]
                    else:
                        output = string

                elif datatype == &amp;quot;BF&amp;quot;:
                    string = self.current_conditions[0].wind_dir
                    if self.isNumeric(string) == True:
                        string = WeatherText.bearing_arrow_font[self.getBearingText(string)]
                    output = string
                elif datatype == &amp;quot;WS&amp;quot;:
                    string = self.current_conditions[0].wind_speed
                    if self.isNumeric(string) == True:
                        if imperial == True:
                            string = self.convertKilometresToMiles(string)
                        string = string + speedunit
                    output = string
                elif datatype == &amp;quot;WG&amp;quot;:
                    string = self.current_conditions[0].wind_gusts
                    if self.isNumeric(string) == True:
                        if imperial == True:
                            string = self.convertKilometresToMiles(string)
                        string = string + speedunit
                    output = string
                elif datatype == &amp;quot;CN&amp;quot;:
                    output = self.current_conditions[0].city
                elif datatype == &amp;quot;SR&amp;quot;:
                    output = self.current_conditions[0].sunrise
                elif datatype == &amp;quot;SS&amp;quot;:
                    output = self.current_conditions[0].sunset
                elif datatype == &amp;quot;MP&amp;quot;:
                    output = self.current_conditions[0].moon_phase
                elif datatype == &amp;quot;MF&amp;quot;:
                    output = WeatherText.conditions_moon_font[self.current_conditions[0].moon_icon]
                elif datatype == &amp;quot;BR&amp;quot;:
                    string = self.current_conditions[0].bar_read
                    if self.isNumeric(string) == True:
                        if imperial == True:
                            string = self.convertMillibarsToInches(string)
                        string = string + pressureunit
                    output = string
                elif datatype == &amp;quot;BD&amp;quot;:
                    output = self.current_conditions[0].bar_desc
                else:
                    output = &amp;quot;\nERROR:Unknown data type requested&amp;quot;

            else: # forecast data

                if endday == None: # if no endday was set use startday
                    endday = startday

                if night == True: # night forecast required

                    for day_number in range(startday, endday+1):

                        if datatype == &amp;quot;DW&amp;quot;:
                            if self.locale == &amp;quot;es&amp;quot;:
                                if shortweekday == True:
                                    output = output + self.getSpaces(spaces) + WeatherText.day_of_week_short_es[self.night_forecast[day_number].day_of_week]
                                else:
                                    output = output + self.getSpaces(spaces) + WeatherText.day_of_week_es[self.night_forecast[day_number].day_of_week]
                            elif self.locale == &amp;quot;fr&amp;quot;:
                                if shortweekday == True:
                                    output = output + self.getSpaces(spaces) + WeatherText.day_of_week_short_fr[self.night_forecast[day_number].day_of_week]
                                else:
                                    output = output + self.getSpaces(spaces) + WeatherText.day_of_week_fr[self.night_forecast[day_number].day_of_week]
                            else:
                                if shortweekday == True:
                                    output = output + self.getSpaces(spaces) + WeatherText.day_of_week_short[self.night_forecast[day_number].day_of_week]
                                else:
                                    output = output + self.getSpaces(spaces) + WeatherText.day_of_week[self.night_forecast[day_number].day_of_week]
                        elif datatype == &amp;quot;WF&amp;quot;: # weather font
                            output = output + self.getSpaces(spaces) + WeatherText.conditions_weather_font[self.night_forecast[day_number].condition_code]
                        elif datatype == &amp;quot;LT&amp;quot;:
                            string = self.night_forecast[day_number].low
                            if self.isNumeric(string) == True:
                                if imperial == True:
                                    string = self.convertCelsiusToFahrenheit(string)
                                string = string + tempunit
                            output = output + self.getSpaces(spaces) + string

                        elif datatype == &amp;quot;HT&amp;quot;:
                            string = self.night_forecast[day_number].high
                            if self.isNumeric(string) == True:
                                if imperial == True:
                                    string = self.convertCelsiusToFahrenheit(string)
                                string = string + tempunit
                            output = output + self.getSpaces(spaces) + string
                        elif datatype == &amp;quot;CC&amp;quot;:
                            if self.locale == &amp;quot;es&amp;quot;:
                                output = output + self.getSpaces(spaces) + WeatherText.conditions_text_es[self.night_forecast[day_number].condition_code]
                            elif self.locale == &amp;quot;fr&amp;quot;:
                                output = output + self.getSpaces(spaces) + WeatherText.conditions_text_fr[self.night_forecast[day_number].condition_code]
                            else:
                                output = output + self.getSpaces(spaces) + WeatherText.conditions_text[self.night_forecast[day_number].condition_code]
                        elif datatype == &amp;quot;CT&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.night_forecast[day_number].condition_text
                        elif datatype == &amp;quot;PC&amp;quot;:
                            string = self.night_forecast[day_number].precip
                            if self.isNumeric(string) == True:
                                string = string + u&amp;quot;%&amp;quot;
                            output = output + self.getSpaces(spaces) + string
                        elif datatype == &amp;quot;HM&amp;quot;:
                            string = self.night_forecast[day_number].humidity
                            if self.isNumeric(string) == True:
                                string = string + u&amp;quot;%&amp;quot;
                            output = output + self.getSpaces(spaces) + string
                        elif datatype == &amp;quot;WD&amp;quot;:
                            string = self.night_forecast[day_number].wind_dir
                            if self.locale == &amp;quot;es&amp;quot;:
                                output = output + self.getSpaces(spaces) + WeatherText.bearing_text_es[string]
                            elif self.locale == &amp;quot;fr&amp;quot;:
                                output = output + self.getSpaces(spaces) + WeatherText.bearing_text_fr[string]
                            else:
                                output = output + self.getSpaces(spaces) + string

                        elif datatype == &amp;quot;BF&amp;quot;:
                            output = output + self.getSpaces(spaces) + WeatherText.bearing_arrow_font[self.night_forecast[day_number].wind_dir]
                        elif datatype == &amp;quot;WS&amp;quot;:
                            string = self.night_forecast[day_number].wind_speed
                            if self.isNumeric(string) == True:
                                if imperial == True:
                                    string = self.convertKilometresToMiles(string)
                                string = string + speedunit
                            output = output + self.getSpaces(spaces) + string
                        elif datatype == &amp;quot;WG&amp;quot;:
                            string = self.night_forecast[day_number].wind_gusts
                            if self.isNumeric(string) == True:
                                if imperial == True:
                                    string = self.convertKilometresToMiles(string)
                                string = string + speedunit
                            output = output + self.getSpaces(spaces) + string
                        elif datatype == &amp;quot;CN&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.night_forecast[day_number].city
                        elif datatype == &amp;quot;SR&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.night_forecast[day_number].sunrise
                        elif datatype == &amp;quot;SS&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.night_forecast[day_number].sunset
                        elif datatype == &amp;quot;MP&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.night_forecast[day_number].moon_phase
                        elif datatype == &amp;quot;MF&amp;quot;:
                            output = output + self.getSpaces(spaces) + WeatherText.conditions_moon_font[self.night_forecast[day_number].moon_icon]
                        elif datatype == &amp;quot;BR&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.night_forecast[day_number].bar_read
                        elif datatype == &amp;quot;BD&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.night_forecast[day_number].bar_desc
                        else:
                            output = &amp;quot;\nERROR:Unknown data type requested&amp;quot;
                            break

                else: # day forecast wanted

                    for day_number in range(startday, endday+1):

                        if datatype == &amp;quot;DW&amp;quot;:
                            if self.locale == &amp;quot;es&amp;quot;:
                                if shortweekday == True:
                                    output = output + self.getSpaces(spaces) + WeatherText.day_of_week_short_es[self.day_forecast[day_number].day_of_week]
                                else:
                                    output = output + self.getSpaces(spaces) + WeatherText.day_of_week_es[self.day_forecast[day_number].day_of_week]
                            elif self.locale == &amp;quot;fr&amp;quot;:
                                if shortweekday == True:
                                    output = output + self.getSpaces(spaces) + WeatherText.day_of_week_short_fr[self.day_forecast[day_number].day_of_week]
                                else:
                                    output = output + self.getSpaces(spaces) + WeatherText.day_of_week_fr[self.day_forecast[day_number].day_of_week]
                            else:
                                if shortweekday == True:
                                    output = output + self.getSpaces(spaces) + WeatherText.day_of_week_short[self.day_forecast[day_number].day_of_week]
                                else:
                                    output = output + self.getSpaces(spaces) + WeatherText.day_of_week[self.day_forecast[day_number].day_of_week]
                        elif datatype == &amp;quot;WF&amp;quot;: # weather font
                            output = output + self.getSpaces(spaces) + WeatherText.conditions_weather_font[self.day_forecast[day_number].condition_code]
                        elif datatype == &amp;quot;LT&amp;quot;:
                            string = self.day_forecast[day_number].low
                            if self.isNumeric(string) == True:
                                if imperial == True:
                                    string = self.convertCelsiusToFahrenheit(string)
                                string = string + tempunit
                            output = output + self.getSpaces(spaces) + string
                        elif datatype == &amp;quot;HT&amp;quot;:
                            string = self.day_forecast[day_number].high
                            if self.isNumeric(string) == True:
                                if imperial == True:
                                    string = self.convertCelsiusToFahrenheit(string)
                                string = string + tempunit
                            output = output + self.getSpaces(spaces) + string
                        elif datatype == &amp;quot;CC&amp;quot;:
                            if self.locale == &amp;quot;es&amp;quot;:
                                output = output + self.getSpaces(spaces) + WeatherText.conditions_text_es[self.day_forecast[day_number].condition_code]
                            elif self.locale == &amp;quot;fr&amp;quot;:
                                output = output + self.getSpaces(spaces) + WeatherText.conditions_text_fr[self.day_forecast[day_number].condition_code]
                            else:
                                output = output + self.getSpaces(spaces) + WeatherText.conditions_text[self.day_forecast[day_number].condition_code]
                        elif datatype == &amp;quot;CT&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.day_forecast[day_number].condition_text
                        elif datatype == &amp;quot;PC&amp;quot;:
                            string = self.day_forecast[day_number].precip
                            if self.isNumeric(string) == True:
                                string = string + u&amp;quot;%&amp;quot;
                            output = output + self.getSpaces(spaces) + string
                        elif datatype == &amp;quot;HM&amp;quot;:
                            string = self.day_forecast[day_number].humidity
                            if self.isNumeric(string) == True:
                                string = string + u&amp;quot;%&amp;quot;
                            output = output + self.getSpaces(spaces) + string
                        elif datatype == &amp;quot;WD&amp;quot;:
                            string = self.day_forecast[day_number].wind_dir

                            if self.locale == &amp;quot;es&amp;quot;:
                                output = output + self.getSpaces(spaces) + WeatherText.bearing_text_es[string]
                            elif self.locale == &amp;quot;fr&amp;quot;:
                                output = output + self.getSpaces(spaces) + WeatherText.bearing_text_fr[string]
                            else:
                                output = output + self.getSpaces(spaces) + string

                        elif datatype == &amp;quot;BF&amp;quot;:
                            output = output + self.getSpaces(spaces) + WeatherText.bearing_arrow_font[self.day_forecast[day_number].wind_dir]
                        elif datatype == &amp;quot;WS&amp;quot;:
                            string = self.day_forecast[day_number].wind_speed
                            if self.isNumeric(string) == True:
                                if imperial == True:
                                    string = self.convertKilometresToMiles(string)
                                string = string + speedunit
                            output = output + self.getSpaces(spaces) + string
                        elif datatype == &amp;quot;WG&amp;quot;:
                            string = self.day_forecast[day_number].wind_gusts
                            if self.isNumeric(string) == True:
                                if imperial == True:
                                    string = self.convertKilometresToMiles(string)
                                string = string + speedunit
                            output = output + self.getSpaces(spaces) + string
                        elif datatype == &amp;quot;CN&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.day_forecast[day_number].city
                        elif datatype == &amp;quot;SR&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.day_forecast[day_number].sunrise
                        elif datatype == &amp;quot;SS&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.day_forecast[day_number].sunset
                        elif datatype == &amp;quot;MP&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.day_forecast[day_number].moon_phase
                        elif datatype == &amp;quot;MF&amp;quot;:
                            output = output + self.getSpaces(spaces) + WeatherText.conditions_moon_font[self.day_forecast[day_number].moon_icon]
                        elif datatype == &amp;quot;BR&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.day_forecast[day_number].bar_read
                        elif datatype == &amp;quot;BD&amp;quot;:
                            output = output + self.getSpaces(spaces) + self.day_forecast[day_number].bar_desc
                        else:
                            output = u&amp;quot;\nERROR:Unknown data type requested&amp;quot;
                            break

            output = u&amp;quot;&amp;quot;+output.strip(u&amp;quot; &amp;quot;) # lose leading/trailing spaces
            return output

        #except:
            #print &amp;quot;getOutputText:Unexpected error: &amp;quot;, sys.exc_info()[0]


    def getOutputTextFromTemplate(self,template):
        #try:

            # keys to template data
            DATATYPE_KEY = &amp;quot;--datatype=&amp;quot;
            STARTDAY_KEY = &amp;quot;--startday=&amp;quot;
            ENDDAY_KEY = &amp;quot;--endday=&amp;quot;
            NIGHT_KEY = &amp;quot;--night&amp;quot;
            SHORTWEEKDAY_KEY = &amp;quot;--shortweekday&amp;quot;
            IMPERIAL_KEY = &amp;quot;--imperial&amp;quot;
            HIDEUNITS_KEY = &amp;quot;--hideunits&amp;quot;
            SPACES_KEY = &amp;quot;--spaces=&amp;quot;

            output = u&amp;quot;&amp;quot;

            optionfound = False

            #load the file
            try:
                fileinput = open(self.options.template)
                template = fileinput.read()
                fileinput.close()
            except:
                output = u&amp;quot;Template file no found!&amp;quot;

            templatelist = self.getTemplateList(template)

            # lets walk through the template list and determine the output for each item found
            for i in range(0,len(templatelist)-1):

                pos = templatelist[i].find(DATATYPE_KEY)
                if pos != -1:
                    optionfound = True
                    pos = pos + len(DATATYPE_KEY)
                    datatype = templatelist[i][pos:pos+4].strip(&amp;quot;}&amp;quot;).strip(&amp;quot;{&amp;quot;).strip(&amp;quot;-&amp;quot;).strip(&amp;quot; &amp;quot;)
                else:
                    datatype = None

                pos = templatelist[i].find(STARTDAY_KEY)
                if pos != -1:
                    optionfound = True
                    pos = pos + len(STARTDAY_KEY)
                    startday = int(templatelist[i][pos:pos+4].strip(&amp;quot;}&amp;quot;).strip(&amp;quot;{&amp;quot;).strip(&amp;quot;-&amp;quot;).strip(&amp;quot; &amp;quot;))
                else:
                    startday = None

                pos = templatelist[i].find(ENDDAY_KEY)
                if pos != -1:
                    optionfound = True
                    pos = pos + len(ENDDAY_KEY)
                    endday = int(templatelist[i][pos:pos+4].strip(&amp;quot;}&amp;quot;).strip(&amp;quot;{&amp;quot;).strip(&amp;quot;-&amp;quot;).strip(&amp;quot; &amp;quot;))
                else:
                    endday = None

                pos = templatelist[i].find(NIGHT_KEY)
                if pos != -1:
                    optionfound = True
                    night = True
                else:
                    night = False

                pos = templatelist[i].find(SHORTWEEKDAY_KEY)
                if pos != -1:
                    optionfound = True
                    shortweekday = True
                else:
                    shortweekday = False

                pos = templatelist[i].find(IMPERIAL_KEY)
                if pos != -1:
                    optionfound = True
                    imperial = True
                else:
                    imperial = False

                pos = templatelist[i].find(HIDEUNITS_KEY)
                if pos != -1:
                    optionfound = True
                    hideunits = True
                else:
                    hideunits = False

                pos = templatelist[i].find(SPACES_KEY)
                if pos != -1:
                    optionfound = True
                    pos = pos + len(SPACES_KEY)
                    spaces = int(templatelist[i][pos:pos+4].strip(&amp;quot;}&amp;quot;).strip(&amp;quot;{&amp;quot;).strip(&amp;quot;-&amp;quot;).strip(&amp;quot; &amp;quot;))
                else:
                    spaces = 1

                if optionfound == True:
                    templatelist[i] = self.getOutputText(datatype,startday,endday,night,shortweekday,imperial,hideunits,spaces)
                    optionfound = False

            # go through the list concatenating the output now that it's been populated
            for item in templatelist:
                output = output + item

            return output

        #except:
            #print &amp;quot;getOutputTextFromTemplate:Unexpected error: &amp;quot;, sys.exc_info()[0]


    def fetchData(self):

        # always fetch metric data, use conversation functions on this data
        file_path_current = self.TEMP_FILEPATH_CURRENT.replace(&amp;quot;LOCATION&amp;quot;,self.options.location)
        file_path_dayforecast = self.TEMP_FILEPATH_DAYFORECAST.replace(&amp;quot;LOCATION&amp;quot;,self.options.location)
        file_path_nightforecast = self.TEMP_FILEPATH_NIGHTFORECAST.replace(&amp;quot;LOCATION&amp;quot;,self.options.location)

        if self.isConnectionAvailable() == False:
            if os.path.exists(file_path_current):
                RefetchData = False
            else: # no connection, no cache, bang!
                print &amp;quot;No internet connection is available and no cached weather data exists.&amp;quot;
        elif self.options.refetch == True:
            RefetchData = True
        else:
                # does the data need retrieving again?
                if os.path.exists(file_path_current):
                    lastmodDate = time.localtime(os.stat(file_path_current)[ST_MTIME])
                expiryDate = (datetime.datetime.today() - datetime.timedelta(minutes=self.EXPIRY_MINUTES)).timetuple()

                if expiryDate &amp;gt; lastmodDate:
                    RefetchData = True
                else:
                    RefetchData = False
            else:
                RefetchData = True


                                                                # fetch the current conditions data, either from the website or by 'unpickling'
            if RefetchData == True:

            # obtain current conditions data from xoap service
            try:

                # http://xoap.weather.com/weather/local/UKXX0103?cc=*=5=xoap=xoap=1061785028=e374effbfd74930b

                url = 'http://xoap.weather.com/weather/local/' + self.options.location + '?cc=*=8=xoap=xoap=1061785028=e374effbfd74930b=m'
                if self.options.verbose == True:
                    print &amp;gt;&amp;gt; sys.stdout, &amp;quot;fetching weather data from &amp;quot;,url

                usock = urllib2.urlopen(url)
                xml = usock.read()
                usock.close()
                self.weatherxmldoc = minidom.parseString(xml)
            except:
                print &amp;quot;fetchData:Unexpected error: &amp;quot;, sys.exc_info()[0]
                print &amp;quot;Unable to contact weather source for current conditions&amp;quot;

            # tell the user if the location is bad...
            found = xml.find(&amp;quot;Invalid location provided&amp;quot;)
            if found != -1:
                print &amp;quot;Invalid location provided&amp;quot;

            # interrogate weather data, load into class structure and pickle it
            try:

                # prepare weather data lists
                self.current_conditions = []
                self.day_forecast = []
                self.night_forecast = []

                # collect general data
                weather_n = self.weatherxmldoc.documentElement
                location_n = weather_n.getElementsByTagName('loc')[0]
                city_n = location_n.getElementsByTagName('dnam')[0]
                city = self.getText(city_n.childNodes)

                # collect current conditions data
                day_of_week = u&amp;quot;Today&amp;quot;
                precip = u&amp;quot;N/A&amp;quot;
                sunrise_n = location_n.getElementsByTagName('sunr')[0]
                sunrise = self.getText(sunrise_n.childNodes)
                sunset_n = location_n.getElementsByTagName('suns')[0]
                sunset = self.getText(sunset_n.childNodes)
                current_condition_n = weather_n.getElementsByTagName('cc')[0]
                current_desc_n = current_condition_n.getElementsByTagName('t')[0]
                current_desc = self.getText(current_desc_n.childNodes)
                current_code_n = current_condition_n.getElementsByTagName('icon')[0]
                current_code = self.getText(current_code_n.childNodes)
                current_temp_n = current_condition_n.getElementsByTagName('tmp')[0]
                current_temp = self.getText(current_temp_n.childNodes)
                current_temp_feels_n = current_condition_n.getElementsByTagName('flik')[0]
                current_temp_feels = self.getText(current_temp_feels_n.childNodes)
                bar_n = current_condition_n.getElementsByTagName('bar')[0]
                bar_read_n = bar_n.getElementsByTagName('r')[0]
                bar_read = self.getText(bar_read_n.childNodes)
                bar_desc_n = bar_n.getElementsByTagName('d')[0]
                bar_desc = self.getText(bar_desc_n.childNodes)
                wind_n = current_condition_n.getElementsByTagName('wind')[0]
                wind_speed_n = wind_n.getElementsByTagName('s')[0]
                wind_speed = self.getText(wind_speed_n.childNodes)
                wind_gust_n = wind_n.getElementsByTagName('gust')[0]
                wind_gusts = self.getText(wind_gust_n.childNodes)
                wind_dir_n = wind_n.getElementsByTagName('d')[0]
                wind_direction = self.getText(wind_dir_n.childNodes)
                humidity_n = current_condition_n.getElementsByTagName('hmid')[0]
                humidity = self.getText(humidity_n.childNodes)
                moon_n = current_condition_n.getElementsByTagName('moon')[0]
                moon_icon_n = moon_n.getElementsByTagName('icon')[0]
                moon_icon = self.getText(moon_icon_n.childNodes)
                moon_phase_n = moon_n.getElementsByTagName('t')[0]
                moon_phase = self.getText(moon_phase_n.childNodes)
                current_conditions_data = WeatherData(day_of_week, current_temp_feels, current_temp, current_code, current_desc, precip, humidity, wind_direction, wind_speed, wind_gusts, city, sunrise, sunset, moon_phase, moon_icon, bar_read, bar_desc)
                self.current_conditions.append(current_conditions_data)

                # collect forecast data
                bar_read = u&amp;quot;N/A&amp;quot;
                bar_desc = u&amp;quot;N/A&amp;quot;
                moon_phase = u&amp;quot;N/A&amp;quot;
                moon_icon = u&amp;quot;na&amp;quot;
                forecast_n = weather_n.getElementsByTagName('dayf')[0]
                day_nodes = forecast_n.getElementsByTagName('day')

                for day in day_nodes:
                    day_of_week = day.getAttribute('t')
                    day_of_year = day.getAttribute('dt')
                    high_temp_n = day.getElementsByTagName('hi')[0]
                    high_temp = self.getText(high_temp_n.childNodes)
                    low_temp_n = day.getElementsByTagName('low')[0]
                    low_temp = self.getText(low_temp_n.childNodes)

                    sunrise_n = day.getElementsByTagName('sunr')[0]
                    sunrise = self.getText(sunrise_n.childNodes)
                    sunset_n = day.getElementsByTagName('suns')[0]
                    sunset = self.getText(sunset_n.childNodes)

                    # day forecast specific data
                    daytime_n = day.getElementsByTagName('part')[0] # day
                    condition_code_n = daytime_n.getElementsByTagName('icon')[0]
                    condition_code = self.getText(condition_code_n.childNodes)
                    condition_n = daytime_n.getElementsByTagName('t')[0]
                    condition = self.getText(condition_n.childNodes)
                    precip_n = daytime_n.getElementsByTagName('ppcp')[0]
                    precip = self.getText(precip_n.childNodes)
                    humidity_n = daytime_n.getElementsByTagName('hmid')[0]
                    humidity = self.getText(humidity_n.childNodes)
                    wind_n = daytime_n.getElementsByTagName('wind')[0]
                    wind_speed_n = wind_n.getElementsByTagName('s')[0]
                    wind_speed = self.getText(wind_speed_n.childNodes)
                    wind_direction_n = wind_n.getElementsByTagName('t')[0]
                    wind_direction = self.getText(wind_direction_n.childNodes)
                    wind_gusts_n = wind_n.getElementsByTagName('gust')[0]
                    wind_gusts = self.getText(wind_gusts_n.childNodes)
                    day_forecast_data = WeatherData(day_of_week, low_temp, high_temp, condition_code, condition, precip, humidity, wind_direction, wind_speed, wind_gusts, city, sunrise, sunset, moon_phase, moon_icon, bar_read, bar_desc)
                    self.day_forecast.append(day_forecast_data)

                    # night forecast specific data
                    daytime_n = day.getElementsByTagName('part')[1] # night
                    condition_code_n = daytime_n.getElementsByTagName('icon')[0]
                    condition_code = self.getText(condition_code_n.childNodes)
                    condition_n = daytime_n.getElementsByTagName('t')[0]
                    condition = self.getText(condition_n.childNodes)
                    precip_n = daytime_n.getElementsByTagName('ppcp')[0]
                    precip = self.getText(precip_n.childNodes)
                    humidity_n = daytime_n.getElementsByTagName('hmid')[0]
                    humidity = self.getText(humidity_n.childNodes)
                    wind_n = daytime_n.getElementsByTagName('wind')[0]
                    wind_speed_n = wind_n.getElementsByTagName('s')[0]
                    wind_speed = self.getText(wind_speed_n.childNodes)
                    wind_direction_n = wind_n.getElementsByTagName('t')[0]
                    wind_direction = self.getText(wind_direction_n.childNodes)
                    wind_gusts_n = wind_n.getElementsByTagName('gust')[0]
                    wind_gusts = self.getText(wind_gusts_n.childNodes)
                    night_forecast_data = WeatherData(day_of_week, low_temp, high_temp, condition_code, condition, precip, humidity, wind_direction, wind_speed, wind_gusts, city, sunrise, sunset, moon_phase, moon_icon, bar_read, bar_desc)
                    self.night_forecast.append(night_forecast_data)


                # pickle the data for next time!
                fileoutput = open(file_path_current, 'w')
                    pickle.dump(self.current_conditions,fileoutput)
                    fileoutput.close()

                fileoutput = open(file_path_dayforecast, 'w')
                    pickle.dump(self.day_forecast,fileoutput)
                    fileoutput.close()

                fileoutput = open(file_path_nightforecast, 'w')
                    pickle.dump(self.night_forecast,fileoutput)
                    fileoutput.close()

            except:
                print &amp;quot;fetchData:Unexpected error: &amp;quot;, sys.exc_info()[0]
                print &amp;quot;Unable to interrogate the weather data&amp;quot;

        else: # fetch weather data from pickled class files
            if self.options.verbose == True:
                print &amp;gt;&amp;gt; sys.stdout, &amp;quot;fetching weather data from file: &amp;quot;,file_path_current

                fileinput = open(file_path_current, 'r')
            self.current_conditions = pickle.load(fileinput)
            fileinput.close()

            if self.options.verbose == True:
                print &amp;gt;&amp;gt; sys.stdout, &amp;quot;fetching day forecast data from files: &amp;quot;,file_path_dayforecast, file_path_nightforecast

                fileinput = open(file_path_dayforecast, 'r')
            self.day_forecast = pickle.load(fileinput)
            fileinput.close()

            if self.options.verbose == True:
                print &amp;gt;&amp;gt; sys.stdout, &amp;quot;fetching day forecast data from files: &amp;quot;,file_path_nightforecast, file_path_nightforecast

                fileinput = open(file_path_nightforecast, 'r')
            self.night_forecast = pickle.load(fileinput)
            fileinput.close()

    def outputData(self):
        #try:

            if self.options.template != None:

                output = self.getOutputTextFromTemplate(self.options.template)

            else:

                output = self.getOutputText(self.options.datatype,self.options.startday,self.options.endday,self.options.night,self.options.shortweekday,self.options.imperial,self.options.hideunits,self.options.spaces)


            print output.encode(&amp;quot;utf-8&amp;quot;)

        #except:
            #print &amp;quot;outputData:Unexpected error: &amp;quot;, sys.exc_info()[0]

if __name__ == &amp;quot;__main__&amp;quot;:

    parser = CommandLineParser()
    (options, args) = parser.parse_args()

    if options.verbose == True:
        print &amp;gt;&amp;gt; sys.stdout, &amp;quot;location:&amp;quot;,options.location
        print &amp;gt;&amp;gt; sys.stdout, &amp;quot;imperial:&amp;quot;,options.imperial
        print &amp;gt;&amp;gt; sys.stdout, &amp;quot;datatype:&amp;quot;,options.datatype
        print &amp;gt;&amp;gt; sys.stdout, &amp;quot;night:&amp;quot;,options.night
        print &amp;gt;&amp;gt; sys.stdout, &amp;quot;start day:&amp;quot;,options.startday
        print &amp;gt;&amp;gt; sys.stdout, &amp;quot;end day:&amp;quot;,options.endday
        print &amp;gt;&amp;gt; sys.stdout, &amp;quot;spaces:&amp;quot;,options.spaces
        print &amp;gt;&amp;gt; sys.stdout, &amp;quot;verbose:&amp;quot;,options.verbose
        print &amp;gt;&amp;gt; sys.stdout, &amp;quot;refetch:&amp;quot;,options.refetch

    # create new global weather object
    weather = GlobalWeather(options)
    weather.fetchData()
    weather.outputData()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Save this script as &lt;code&gt;conkyForecast.py&lt;/code&gt; and make it executable.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/sd/</id><title>Install fonts using nautilus-actions</title><link href="https://stebalien.com/blog/sd/" rel="alternate"/><updated>2008-05-31T00:00:00+00:00</updated><published>2008-05-31T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;apt:nautilus-actions&quot;&gt;Nautilus-Actions&lt;/a&gt; installs custom entries in the context menu in nautilus. I needed a simple way to install fonts so I wrote a command for nautilus-actions.&lt;/p&gt;&lt;p&gt;Set “Path:” to &lt;code&gt;bash&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Set Parameters to
&lt;code&gt;-c &amp;quot;if zenity --question --text=\&amp;quot;Would you like to install the font system wide?\&amp;quot; --title=\&amp;quot;Install to System\&amp;quot;; then gksudo cp %M /usr/share/fonts/truetype/local/%m; else cp %M ~/.fonts/%m; fi; exit 0;&amp;quot;&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;If you do not want to install nautilus actions you can simply create a Nautilus script.&lt;/p&gt;&lt;p&gt;The contents of the script should be:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;#!/bin/bash
if zenity --question --text=&amp;quot;Would you like to install the font system wide?&amp;quot; --title=&amp;quot;Install to System&amp;quot;; then
     for $i in $NAUTILUS_SCRIPT_SELECTED_FILE_PATHS; do
          gksudo cp $i /usr/share/fonts/truetype/local/$(basename $i)
     done
else
     for $i in $NAUTILUS_SCRIPT_SELECTED_FILE_PATHS; do
          cp $i ~/.fonts/$(basename $i)
     done
fi
exit 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Save this script in ‘~/.gnome2/nautilus-scripts’ and make it executable (&lt;code&gt;chmod u+x&lt;/code&gt;).&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/qalculate-best-calculator-program/</id><title>Qalculate: The best calculator program</title><link href="https://stebalien.com/blog/qalculate-best-calculator-program/" rel="alternate"/><updated>2008-05-25T00:00:00+00:00</updated><published>2008-05-25T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I finaly found the (almost) perfect calculator program, &lt;a href=&quot;apt:qalculate-gtk&quot;&gt;qalculate&lt;/a&gt;. It can solve/factor equations, work with significant figures, work with trigonometry, and much more.&lt;/p&gt;&lt;p&gt;Screenshots:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/qalculate-best-calculator-program/static/qalculatehistory.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/qalculate-best-calculator-program/static/qalculatehistory.th.png&quot; alt=&quot;History&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;https://stebalien.com/blog/qalculate-best-calculator-program/static/qalculatekeypad.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/qalculate-best-calculator-program/static/qalculatekeypad.th.png&quot; alt=&quot;Keypad&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/pcman-file-manager-7z-and-rar-archives/</id><title>PCMan File Manager: 7z and rar archives</title><link href="https://stebalien.com/blog/pcman-file-manager-7z-and-rar-archives/" rel="alternate"/><updated>2008-05-25T00:00:00+00:00</updated><published>2008-05-25T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;By default, pcmanfm will not extract/create 7zip and Rar archives. Extracting rar archives is already implemented but not enabled by default while the ability to extract 7z archives is not implemented at all. To enable the extraction of rar and 7zip archives and the creation of 7zip archives open &lt;code&gt;src/ptk/ptk-file-archiver.c&lt;/code&gt;:&lt;/p&gt;&lt;p&gt;Uncomment:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;       {
           &amp;quot;application/x-rar&amp;quot;,
           NULL,
           &amp;quot;unrar -o- e&amp;quot;,
           &amp;quot;.rar&amp;quot;, TRUE
       }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And add this below:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;       {
           &amp;quot;application/x-7z-compressed&amp;quot;,
           &amp;quot;7zr a -bd -y&amp;quot;,
           &amp;quot;7zr x -bd -y&amp;quot;,
           &amp;quot;.7z&amp;quot;, TRUE
       }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, insert a comma, after the end bracket of the rar section.&lt;/p&gt;&lt;p&gt;The archive handlers section should endup looking like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;const ArchiveHandler handlers[]=
   {
       {
           &amp;quot;application/x-bzip-compressed-tar&amp;quot;,
           &amp;quot;tar --bzip2 -cvf&amp;quot;,
           &amp;quot;tar --bzip2 -xvf&amp;quot;,
           &amp;quot;.tar.bz2&amp;quot;, TRUE
       },
       {
           &amp;quot;application/x-compressed-tar&amp;quot;,
           &amp;quot;tar -czvf&amp;quot;,
           &amp;quot;tar -xzvf&amp;quot;,
           &amp;quot;.tar.gz&amp;quot;, TRUE
       },
       {
           &amp;quot;application/zip&amp;quot;,
           &amp;quot;zip -r&amp;quot;,
           &amp;quot;unzip&amp;quot;,
           &amp;quot;.zip&amp;quot;, TRUE
       },
       {
           &amp;quot;application/x-tar&amp;quot;,
           &amp;quot;tar -cvf&amp;quot;,
           &amp;quot;tar -xvf&amp;quot;,
           &amp;quot;.tar&amp;quot;, TRUE
       },
       {
           &amp;quot;application/x-rar&amp;quot;,
           NULL,
           &amp;quot;unrar -o- e&amp;quot;,
           &amp;quot;.rar&amp;quot;, TRUE
       },
       {
           &amp;quot;application/x-7z-compressed&amp;quot;,
           &amp;quot;7zr a -bd -y&amp;quot;,
           &amp;quot;7zr x -bd -y&amp;quot;,
           &amp;quot;.7z&amp;quot;, TRUE
       }
   };
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Compile with a standard &lt;code&gt;./configure; make; make install&lt;/code&gt; or &lt;code&gt;./configure; make; checkinstall&lt;/code&gt;.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/install-flash-player-10-in-liferea/</id><title>Install Flash Player 10 in liferea</title><link href="https://stebalien.com/blog/install-flash-player-10-in-liferea/" rel="alternate"/><updated>2008-05-21T00:00:00+00:00</updated><published>2008-05-21T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Installing flash player 10 in Ubuntu does not install it for &lt;a href=&quot;apt:liferea&quot;&gt;Liferea&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The fix for this is very simple:&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;&lt;p&gt;First install Flash Player 10 using this &lt;a href=&quot;https://web.archive.org/web/20100330083137/http://www.ubuntu-unleashed.com/2008/05/howto-install-flash-player-10-astro.html&quot;&gt;Tutorial&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Next execute this command in the terminal:&lt;/p&gt;&lt;pre&gt;&lt;code&gt; sudo ln -s /usr/lib/firefox-addons/plugins/libflashplayer.so \
            /usr/lib/xulrunner-addons/plugins/libflashplayer.so
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Flash Player 10 should now work in Liferea.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/tip-purge-auto-installed-packages-with/</id><title>Tip: Purge Auto-installed packages with Aptitude</title><link href="https://stebalien.com/blog/tip-purge-auto-installed-packages-with/" rel="alternate"/><updated>2008-05-16T00:00:00+00:00</updated><published>2008-05-16T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;When switching from apt-get to aptitude, I discovered that aptitude, unlike apt-get, does not purge configuration files of auto-removed packages when given the command &lt;code&gt;aptitude purge package&lt;/code&gt;. The fix for
this is very simple.&lt;/p&gt;&lt;p&gt;In order to purge configuration files &lt;strong&gt;only&lt;/strong&gt; when the purge command is given you must either specify the &lt;code&gt;--purge-unused&lt;/code&gt; option in the command or create an alias. To create an alias add this &lt;code&gt;alias command='sudo aptitude purge --purge-unused'&lt;/code&gt; to &lt;code&gt;~/.bashrc&lt;/code&gt;. &lt;em&gt;Command&lt;/em&gt; is the command that you would like to assign for purging the packages.&lt;/p&gt;&lt;p&gt;To &lt;strong&gt;always&lt;/strong&gt; purge configuration files for auto-removed packages add &lt;code&gt;Aptitude::Purge-Unused=true&lt;/code&gt; to &lt;code&gt;/etc/apt/apt.conf.d/05aptitude&lt;/code&gt;. This will cause aptitude to always purge configuration files for &lt;strong&gt;all&lt;/strong&gt; auto-removed packages regardless of weather or not the command was &lt;code&gt;aptitude purge&lt;/code&gt; or &lt;code&gt;aptitude remove&lt;/code&gt;.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/rarcrack-crack-zip-rar-and-7z-files/</id><title>Rarcrack: Crack zip, rar, and 7z files</title><link href="https://stebalien.com/blog/rarcrack-crack-zip-rar-and-7z-files/" rel="alternate"/><updated>2008-05-13T00:00:00+00:00</updated><published>2008-05-13T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I have just tested &lt;a href=&quot;http://rarcrack.sourceforge.net/&quot;&gt;rarcrack&lt;/a&gt; and love it. Rarcrack cracks password protected rar, 7z, and zip archives. Sadly, it’s not in the Ubuntu repository. I have therefor compiled a deb: &lt;a href=&quot;https://stebalien.com/blog/rarcrack-crack-zip-rar-and-7z-files/static/rarcrack.deb&quot;&gt;rarcrack.deb&lt;/a&gt;. I have not included this deb in my PPA because I compiled it with &lt;a href=&quot;http://code.google.com/p/debianpackagemaker/&quot;&gt;debianpackagemaker&lt;/a&gt;.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/make-firefox-3-autocomplete-bar-work/</id><title>Make Firefox 3's autocomplete bar work with a dark theme</title><link href="https://stebalien.com/blog/make-firefox-3-autocomplete-bar-work/" rel="alternate"/><updated>2008-05-03T00:00:00+00:00</updated><published>2008-05-03T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I use MurrinaAngustifolium as my gtk theme but, because it is a dark theme, it does not always play nicely with other applications. One problem that I have finally fixed is the drop-down auto-complete menu for the location bar. Put the following code in your &lt;code&gt;userChrome.css&lt;/code&gt; file (or use stylish).&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-css&quot;&gt;@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);
.ac-comment {
  font-size: 100% !important;
  color: #dddddd !important;
}
.ac-comment[selected=&amp;quot;true&amp;quot;] {
  color: #56aaff !important;
}

.ac-url-text {
  font-size: 100% !important;
  color: #555555 !important;
}

.ac-url-text[selected=&amp;quot;true&amp;quot;] {
  color: #666666 !important;
}
.autocomplete-richlistbox {
  background: #1a1a1a !important;
}
.autocomplete-richlistitem[selected=&amp;quot;true&amp;quot;] {
  background: #000000 !important;
}
tooltip {
  background-color: #1a1a1a !important;
  color: #ffffff !important;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This code also makes some of the tooltips black.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/tip-pause-program-not-started-from/</id><title>Tip: Pause a program not started from the command line</title><link href="https://stebalien.com/blog/tip-pause-program-not-started-from/" rel="alternate"/><updated>2008-04-29T00:00:00+00:00</updated><published>2008-04-29T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;This tip is a follow-up from the last one. To pause a program that was not started from a terminal, simply go to the &lt;a href=&quot;apt:gnome-system-manager&quot;&gt;gnome-system-manager&lt;/a&gt;, right click, and select stop. Select continue to resume a paused process.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/tip-pause-command-line-program/</id><title>Tip: Pause a command line program</title><link href="https://stebalien.com/blog/tip-pause-command-line-program/" rel="alternate"/><updated>2008-04-28T00:00:00+00:00</updated><published>2008-04-28T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;This trick is known by many but it can’t hurt to mention it once more. I
sometimes find myself in the middle of running a program in the command
line but would like to check on something before I continue. The easiest
way to pause a program in the command line is to hit &lt;code&gt;Control+Z&lt;/code&gt;. To
restart the command simply type &lt;code&gt;fg&lt;/code&gt;. Do not close the terminal window
or restart your computer because the paused program will close.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/ubuntu-hardy-upgrade-status-problamatic/</id><title>Ubuntu hardy upgrade status: Buggy but working</title><link href="https://stebalien.com/blog/ubuntu-hardy-upgrade-status-problamatic/" rel="alternate"/><updated>2008-04-25T00:00:00+00:00</updated><published>2008-04-25T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I ran into a few problems during the upgrade but all of them were fixable.&lt;/p&gt;&lt;p&gt;Partial Upgrade fix: Downgrade non-ubuntu packages to their ubuntu versions if an ubuntu version exists. My system refused to complete the upgrade (and I had to upgrade it through aptitude) because of a few package conflicts (mostly from the schmidtke repository).&lt;/p&gt;&lt;p&gt;Numpad Fix: My computers numpad stopped working after upgrading. Somehow an acceptability option had been turned on. To fix this go to &lt;code&gt;System &amp;gt; Preferences &amp;gt; Keyboard&lt;/code&gt; and go to the mouse keys tab and uncheck the checkbox.&lt;/p&gt;&lt;p&gt;Remove custom G15 drivers: A while back I had installed the G15 drivers based on a tutorial posted on the ubuntu wiki. These drivers should now be uninstalled as Ubuntu Hardy comes with its own drivers.&lt;/p&gt;&lt;p&gt;Everything appears to be working fine now.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/tip-faster-ubuntu-upgrade/</id><title>Tip: Faster Ubuntu upgrade</title><link href="https://stebalien.com/blog/tip-faster-ubuntu-upgrade/" rel="alternate"/><updated>2008-04-24T00:00:00+00:00</updated><published>2008-04-24T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I am currently upgrading to Ubuntu 8.04 and so far so good. For the past releases I have upgraded from the standard server but that was very slow (8 hour download).&lt;/p&gt;&lt;p&gt;Fix: Use another server.&lt;/p&gt;&lt;p&gt;My current download is almost complete and my download speed is currently at 164kb/s (my max, thanks AT). To change the download server, simply open &lt;code&gt;System &amp;gt; Administration &amp;gt; Software Sources&lt;/code&gt; and select download from other. Pick a random server in your country and hope for the best (the pick the best server button does not find the least bogged down server, it finds the closest server). The Ubuntu teem should make this happen automatically. The System Updater should find the mirror with the greatest download speed and use that server for its upgrade.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/command-in-linux/</id><title>'Pause' command in linux</title><link href="https://stebalien.com/blog/command-in-linux/" rel="alternate"/><updated>2008-04-24T00:00:00+00:00</updated><published>2008-04-24T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Windows comes with a very useful &lt;code&gt;pause&lt;/code&gt; command. Linux does not. This can be problematic when running programs in the terminal from the GUI. If a program runs in a terminal but did not originated in the terminal (i.e. you double click on a file in nautilus and select run in terminal) the terminal window closes immediately when the program finishes. This makes it impossible to read the output from the terminal.&lt;/p&gt;&lt;p&gt;This function can be added to your &lt;code&gt;~/.bashrc&lt;/code&gt; file:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;function pause() {
   test $*  read -p &amp;quot;$*&amp;quot; || read -p &amp;quot;Press enter to continue...&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I found this code on &lt;a href=&quot;http://www.cyberciti.biz/tips/linux-unix-pause-command.html&quot;&gt;www.cyberciti.biz&lt;/a&gt; but edited it to allow for no arguments.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/imageshack-media-hosting-for-web/</id><title>Imageshack: Media hosting for the web</title><link href="https://stebalien.com/blog/imageshack-media-hosting-for-web/" rel="alternate"/><updated>2008-04-23T00:00:00+00:00</updated><published>2008-04-23T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I have seen &lt;a href=&quot;http://imageshack.us/&quot;&gt;imageshack&lt;/a&gt; used a lot but have never tried it myself until now.&lt;/p&gt;&lt;h2&gt;Problem&lt;/h2&gt;&lt;p&gt;Bloggers image hosting is limited (100mb) and does not work with &lt;a href=&quot;http://www.lokeshdhakar.com/projects/lightbox2/&quot;&gt;lightbox&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;Solution&lt;/h2&gt;&lt;p&gt;Imageshack works great with blogger and lightbox. It allows for easy posting of small thumbnails that link to the bigger image. Normally the thumbnail would link to a web page containing the full version of the image but this is not compatible with lightbox (lightbox needs the actual image). The fix is very easy: Simply change the image link to the thumbnail link but remove the &lt;code&gt;.th&lt;/code&gt; from in front of the extension.&lt;/p&gt;&lt;p&gt;My &lt;s&gt;main&lt;/s&gt; complaint is that the thumbnails are too small. I could get around this by using the full image and scaling it down with css but this would wast some of my 300mb per image per hour bandwidth (not really a problem now but may become problematic with more readers).&lt;/p&gt;&lt;p&gt;&lt;em&gt;Edit: My &lt;strong&gt;main&lt;/strong&gt; complaint is that they lose images. Imageshack (along with pretty much every other media hosting site) is great for forum posts, but not for semi-permanent blogs.&lt;/em&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/howto-create-floating-sidebar-on-left/</id><title>HOWTO: Create a floating sidebar on the left (Minima)</title><link href="https://stebalien.com/blog/howto-create-floating-sidebar-on-left/" rel="alternate"/><updated>2008-04-22T00:00:00+00:00</updated><published>2008-04-22T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;It took me a while to get the floating sidebar on the left of this page working well so I decided to write this post in order to save others time and frustration. I made this tutorial for the Minima template but you could modify it in order to work with any template.&lt;/p&gt;&lt;p&gt;Add this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-css&quot;&gt;/* Floating sidebar on left */
#fixedsidebar-wrapper {
  position: fixed;
  top: 75px;
  left: 10px;
  width: 100px;
  float: left;
  word-wrap: break-word; /* fix for long text breaking sidebar float in IE */
  overflow: hidden; /* fix for long non-text content breaking IE sidebar float */
}
#big-wrapper {
  margin: 0 150px 0 100px;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Below this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-css&quot;&gt;#sidebar-wrapper {
  width: 220px;
  float: $endSide;
  word-wrap: break-word; /* fix for long text breaking sidebar float in IE */
  overflow: hidden;     /* fix for long non-text content breaking IE sidebar float */
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, to fix the page elements tab…&lt;/p&gt;&lt;p&gt;Add this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-css&quot;&gt;/** Page structure tweaks for layout editor wireframe for floating sidebar on left **/

body#layout #outer-wrapper,
body#layout #header-wrapper,
body#layout #footer {
  width: 500px;
  padding: 0px;
}
body#layout #main-wrapper {
  width: 300px;
}
body#layout #sidebar-wrapper {
  width: 200px;
}
body#layout #newsidebar-wrapper {
  width: 150px;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;…below the previous code.&lt;/p&gt;&lt;p&gt;To remove the dotted line under widgets…&lt;/p&gt;&lt;p&gt;Add this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-css&quot;&gt;#fixedsidebar.sidebar .widget {
  border-bottom-width: 0 !important;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Below this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-css&quot;&gt;.sidebar .widget, .main .widget {
  border-bottom:1px dotted $bordercolor;
  margin:0 0 1.5em;
  padding:0 0 1.5em;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then add &lt;code&gt;&amp;lt;div id=&amp;quot;big-wrapper&amp;quot;&amp;gt;&lt;/code&gt; just under &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. This will make
sure that the floating sidebar will not overlap the other content.&lt;/p&gt;&lt;p&gt;Now in order to actually place the sidebar…&lt;/p&gt;&lt;p&gt;Add this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-html&quot;&gt;      &amp;lt;div id=&amp;quot;fixedsidebar-wrapper&amp;quot;&amp;gt;
   &amp;lt;b:section class=&amp;quot;sidebar&amp;quot; id=&amp;quot;fixedsidebar&amp;quot; preferred=&amp;quot;yes&amp;quot;&amp;gt;
      &amp;lt;b:widget id=&amp;quot;NewProfile&amp;quot; locked=&amp;quot;false&amp;quot; title=&amp;quot;'About&amp;quot; type=&amp;quot;Profile&amp;quot; /&amp;gt;
    &amp;lt;/b:widget&amp;gt;
  &amp;lt;/b:section&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Below this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-html&quot;&gt;    &amp;lt;div id=&amp;quot;content-wrapper&amp;quot;&amp;gt;
 &amp;lt;div id=&amp;quot;crosscol-wrapper&amp;quot; style=&amp;quot;&amp;quot;&amp;gt;
   &amp;lt;b:section class=&amp;quot;crosscol&amp;quot; id=&amp;quot;crosscol&amp;quot; showaddelement=&amp;quot;no&amp;quot; /&amp;gt;
 &amp;lt;/b:section&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, add a &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt; tag before the &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; tag to close the “big-wrapper”&lt;/p&gt;&lt;p&gt;This post is heavily based on this
&lt;a href=&quot;http://tips-for-new-bloggers.blogspot.com/2007/02/three-columns-blogger-template.html&quot;&gt;tutorial&lt;/a&gt;
but creates a fixed (floating) sidebar instead of a sidebar that scrolls
with the rest of the page.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/for-advertisement-free-browsing-apply/</id><title>For advertisement free browsing: apply Adblock Plus</title><link href="https://stebalien.com/blog/for-advertisement-free-browsing-apply/" rel="alternate"/><updated>2008-04-22T00:00:00+00:00</updated><published>2008-04-22T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;http://adblockplus.org/&quot;&gt;Adblock Plus&lt;/a&gt; is a great Firefox extension that blocks advertisements. Advertisements hog bandwidth and are distracting. It is easy to use (right click and select block element).&lt;/p&gt;&lt;p&gt;Here is my adblock plus filter list (save it as a text document and import it).&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-text&quot;&gt;(Adblock Plus 0.7 or higher required) [Adblock]
@@netstream
@@http://www.microsoft.com/
@@http://www.windowsmarketplace.com/
@@|http://www.kiisfm.com/
@@|http://www.apple.com/
@@|https://www.windwardschool.org/
@@|http://iwonderdesigns.com/
@@|http://news.bbc.co.uk/
@@|http://wow.weather.com/
@@|http://www.defensedevices.com/
@@http://www2.gawker.com/assets/minify.php?files=*
@@http://syndication.mmismm.com/
@@http://tags.gawker.com/
*.2o7.net/*
*/shopping_deals*
*/tacoda/*
*_fromoursponsors_*
/(\W|_)(ad(s)?(vert(is(ement|ing|er)?)?)?)(s)?(view|log|image(s)?|Links|counter|serve(r)?|sys|brite|sonar|lengend|side|files)?(\W|_|1)/
/[\W_](blog|get|online)ad(s)?[\W_]/
/[\W_](double|fast)click[\W_]/
/\.geocities.com/js_source/(ygNSLib9|pu5geo).js/
/\D(728|588|468|234|160|120)x(600?|90)\D/
http://*.sageanalyst.net/*
http://*.centrport.net/*
http://*.falkag.net/*
http://*.industrybrains.com/*
http://*.itnation.com/*
http://*.questionmarket.com/*
http://*.spinbox.net/*
http://*.tiser.com.*/
http://*.tribalfusion.com/*
http://*.valueclick.com/*
http://*theglobeandmail.com/adsv3/*
http://ehg*.hitbox.com/*
http://graphics8.nytimes.com/marketing/
http://images.pcworld.com/shared/graphics/bevel_*.gif
http://images.stardock.com/store/*
http://images.usatoday.com/kk/classified/*
http://infospace.abcnews.com/*/adinsert.js
http://media.nyadmcncserve*.com/
http://media.washingtonpost.com/wp-srv/popjs/popup*
http://msnbc.msn.com/#DIV(p2)
http://msnbc.msn.com/#DIV(textSmallGrey)
http://msnbc.msn.com/*#DIV(clr)
http://msnbc.msn.com/*#DIV(w779)
http://pagead2.googlesyndication.com/*
http://rcm.amazon.com/e/cm?t=*
http://servedby.*
http://server.*.liveperson.net/*
http://www.goodsforyou.com/*
http://www.humaxusa.com/*
http://www.insanely-great.com/*/adsb.cgi*
http://www.kiisfm.com/cc-common/CCAS_media/ccas_creative_81152.gif
/.*(-|_|/)promo(-|_).*\.(gif|png|jpg)/
http://www.qksrv.net/*
intellitxt
*/promotions/*
http://us.a1.yimg.com/*
http://download-search.search.com/search?v=og&amp;amp;q=*
http://i.d.com.com/*/*_sponsors_*.gif
http://www.ftjcfx.com/*
|http://*.addfreestats.com/*
/http://feeds\.(\w|\W)+\.com/~[a]/(\w|\W)+\?.=(\w|\W)+/
http://*.casalemedia.com/*
http://www.lduhtrp.net/*
http://www.afcyhf.com/*
http://www.shareasale.com/*
http://3ps.go.com/DynamicAd?*
/[\W|_](((p|P)romo)(tional)?)?(_)?(promo(s)?|banner)(s)?(_)?(up|down|standard|right|left|top|bottom)?([0-9])?\.(gif|png|jpg|bmp)/
http://*.atwola.com/*
http://ccas.clearchannel.com/*&amp;amp;affiliate=*
http://network.business.com/*
http://affiliates.digitalriver.com/*
http://clk.atdmt.com/*
http://www.awltovhc.com/image-*
http://*.adland.ru/*
http://www.macdailynews.com/adpeeps/*
http://rss.sourceforge.net/~a/*
http://cgi.insecure.org/cgi-bin/pro/*
http://affiliates.babylon.com/*
http://view.atdmt.com/*
/.*(_|/)sponsor(s)?((\.(gif|jpg|png))|(/))/
http://bs.serving-sys.com/BurstingPipe/BannerSource.asp?*
*/web_banners/*
*/bin/ad
http://*.google.com/pagead/*
*/images/banner*.png
http://s1.amazon.com/exec/varzea/tipbox/*
*_sweeps*
http://www.conduit.com/banners/*
*adpartner*
http://digg.com/img/feature-*.gif
http://*.freesoftwaremagazine.com/openads/*
http://regmedia.co.uk/*
http://www.siteground.com/img/banners_cust/2*
http://ubuntulinuxhelp.com/hic300250.gif
http://www.tqlkg.com/*
http://groups.google.com/groups/adfetch?*
http://banner.casinotropez.com/*
http://www.speedsuccess.net/*
#*(sponsored_links_article)
#*(ad_bottom)
#*(ad_top)
#*(launchpad-ads-3)
#*(sponsorship)
#*(sponsorshiphack)
#*(promo2)
#*(ad_300)
#*(ad_180)
#*(adbannerblock)
#*(ad34)
#*(googlesyndication)
##div.banner ad + *
google.com#TABLE(cellpadding=9)(border=0)
openssh.com#BLINK
google.com#a(href^=https://www.google.com/ads)
babelfish.yahoo.com#DIV(ovt)
cnet.com#DIV(class=asl_margin)
cnet.com#DIV(class=rb_pft_ad)
#DIV(id=spons_links)
#DIV(class=columnGroup advertisementColumnGroup)
#A(href^=http://www.servage.net/?coupon=)
flickr.com#DIV(id=AdBlock)
youtube.com#DIV(id=leaderboardAd)
google.com#TD(rowspan=5)
google.com##TD[onmouseover=&amp;quot;return ss()&amp;quot;]
google.com#H2(class=sl f)
#DIV(class=sponsor)
youtube.com#NOSCRIPT(class=noscript-show)
google.com#DIV(id=tpa1)
linuxforums.org#DIV(id=topbanner)
linuxforums.org#NOSCRIPT(class=noscript-show)
linuxquestions.org#NOSCRIPT(class=noscript-show)
#DIV(id=rightad)
#DIV(id=topad)(class=ad)
#A(class=sponsoredlink)
#DIV(id=ad-top)
#DIV(class=banner ad)
opendns.com#UL(class=adkeywords)
google.com##TABLE.contentitem[cellspacing=&amp;quot;0&amp;quot;][cellpadding=&amp;quot;5&amp;quot;][border=&amp;quot;0&amp;quot;][bgcolor=&amp;quot;#ffffff&amp;quot;][style]
google.com##TD[nowrap][onmouseover=&amp;quot;return true&amp;quot;]
google.com#*(href^=http://www.google.com/sponsoredlinks?)
builtwith.com#DIV(class=featuredTechnology)
flashlinux.org.uk#DIV(id=portlet-dtm)
#*(id=sponsors)
#DIV(id=ads)
techcrunch.com#A(href=/advertise/)
#A(href^=http://www.google.com/pagead)
dictionary.reference.com#A(href^=http://www.google.com/url?sa=L&amp;amp;ai=)
#IMG(src^=http://ad.uk.doubleclick.net)
#IMG(src^=http://ad.doubleclick.net)
yahoo.com#A(href=http://us.ard.yahoo.com/SIG=12lt27gkl/M=570179.11705344.12177319.9641256/D=yahoo_top/S=2716149:HDLN/_ylt=AvsqK7FgUbxkrtlnjbKs1lP1cSkA/Y=YAHOO/EXP=1193800089/A=5005233/R=0/SIG=14m0dtrqi/*https://www.getsmart.com/refi/qform.asp?bp=ltquickmatch&amp;amp;esourceid=1271910&amp;amp;promo=00221&amp;amp;num=1-866-262-6895&amp;amp;AdType=2&amp;amp;version=45&amp;amp;disable_popups=1&amp;amp;init=1)
howstuffworks.com#A(class=reify-linkifier)
#DIV(class=topbanners)
#DIV(id=holidayad)
#DIV(class=doubleClickAd)
#*(class=doubleClick)
#DIV(class=adText)
guide.opendns.com#DIV(id=noresultsads)
#DIV(class=ad_itext_bar)
search.com#UL(class=sponsored)
experts-exchange.com#DIV(class=ontopBanner)
guide.opendns.com#DIV(id=adbox)
us.f812.mail.yahoo.com#TABLE(id=welcomeAdsContainer)
books.google.com#DIV(class=ad)
weather.yahoo.com#DIV(class=yw-ad)
lifehacker.com#DIV(id=sitemeter)
#DIV(class=advertContainer)
&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><id>https://stebalien.com/blog/taboo-can-read-it-now-read-it-for-later/</id><title>Taboo: Can't read it now? Save it for later.</title><link href="https://stebalien.com/blog/taboo-can-read-it-now-read-it-for-later/" rel="alternate"/><updated>2008-04-21T00:00:00+00:00</updated><published>2008-04-21T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://addons.mozilla.org/en-us/firefox/addon/taboo/&quot;&gt;Taboo&lt;/a&gt; is basically a temporary bookmarks manager. If you have found an article that you find interesting but it is too long you would normally have 3 options.&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Bookmark it.&lt;/li&gt;&lt;li&gt;Leave the tab open and move on.&lt;/li&gt;&lt;li&gt;Close the tab and find it in your history.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;All of these options have one major problem: Clutter. Bookmarks can either be stored in a menu or the bookmarks toolbar. This is fine for sites that are frequented often or are permanent references but temporary bookmarks make the perminant bookmarks harder to find. A separate temporary bookmarks folder is fine but it can be very hard to find a specific bookmark mixed in with all of the other temporary bookmarks. Leaving the bookmark in your tabbar makes sorting through your tabs frustrating. The history is normaly erased after a set period of time ind is a pain to look through.&lt;/p&gt;&lt;p&gt;Taboo solves these problems. If you can’t remember the sites name, you can look for the thumbnail of the site. If you can only remember when you looked at the site you can view your taboos in calendar mode. If you know that you added your taboo recently (within the last 10 or so) you can use the menu attached to the “View Taboos” button as the list is sorted by most recently added. Taboo is a beta so more features should appear as time goes on.&lt;/p&gt;&lt;p&gt;Screenshots:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/taboo-can-read-it-now-read-it-for-later/static/taboocal.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/taboo-can-read-it-now-read-it-for-later/static/taboocal.th.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;https://stebalien.com/blog/taboo-can-read-it-now-read-it-for-later/static/taboogrid.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/taboo-can-read-it-now-read-it-for-later/static/taboogrid.th.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;https://stebalien.com/blog/taboo-can-read-it-now-read-it-for-later/static/taboocalexpanded.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/taboo-can-read-it-now-read-it-for-later/static/taboocalexpanded.th.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/scribefire-cool-but-not-perfict/</id><title>Scribefire: Cool but not perfect</title><link href="https://stebalien.com/blog/scribefire-cool-but-not-perfict/" rel="alternate"/><updated>2008-04-20T00:00:00+00:00</updated><published>2008-04-20T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Scribefire is a very useful extension and can even &lt;strong&gt;upload images&lt;/strong&gt; to blogger but has a few annoying “bugs” for lack of a better word.&lt;/p&gt;&lt;p&gt;Scribefire pre-release and dark-themes (gtk) don’t mix well (it looks very ugly). Scribefire current release works better with dark themes but not perfectly.&lt;/p&gt;&lt;p&gt;Scribefire can upload images but does not upload them to the standard server (it uploads them to “Picasa web albums”) and, unlike the default editor, it simply places a shrunken version of the image on the webpage and does not link it to its bigger version. This is easily fixable but annoying (especially when using lightbox).&lt;/p&gt;&lt;p&gt;Too many features and few options to trim down the UI.&lt;/p&gt;&lt;p&gt;Verdict: Fun but not for me (yet).&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/gnome-blog-debs-no-repository/</id><title>Gnome-Blog debs (no repository)</title><link href="https://stebalien.com/blog/gnome-blog-debs-no-repository/" rel="alternate"/><updated>2008-04-19T00:01:00+00:00</updated><published>2008-04-19T00:01:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Here is the link to the debs from the &lt;a href=&quot;https://stebalien.com/blog/gnome-blog-debs&quot;&gt;Gnome-Blog debs&lt;/a&gt; post if you don’t want to add my repository.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://launchpad.net/~stebalien/+archive/ubuntu/ppa/+files/gnome-blog_0.9.1-3ubuntu2%7Eppa3_all.deb&quot;&gt;gnome-blog&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/gnome-blog-debs/</id><title>Gnome-Blog debs</title><link href="https://stebalien.com/blog/gnome-blog-debs/" rel="alternate"/><updated>2008-04-19T00:00:00+00:00</updated><published>2008-04-19T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I have just uploaded the patched gnome-blog deb and an updated python-gdata deb (gnome-blog requires python-gdata 1.0.1 or up but gutsy only includes 1.0). The python-gdata is unnecessary if you use hardy but is a later version than the one provided by hardy.&lt;/p&gt;&lt;p&gt;My PPA:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;deb http://ppa.launchpad.net/stebalien/ubuntu gutsy main
deb-src http://ppa.launchpad.net/stebalien/ubuntu gutsy main
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Edit: I got the patch from http://bugzilla.gnome.org/show_bug.cgi?id=151291 (thanks to Richard Schwarting)&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/pdf-tools/</id><title>PDF tools</title><link href="https://stebalien.com/blog/pdf-tools/" rel="alternate"/><updated>2008-04-19T00:00:00+00:00</updated><published>2008-04-19T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;A while back I wrote a script that can convert PDFs into several other
formats. It can also print information about PDFs. I recommend you use
this script in conjunction with
&lt;a href=&quot;apt:nautilus-actions&quot;&gt;nautilus-actions&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;Usage&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;./PDFtools.sh [pdf document]
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Screenshots&lt;/h2&gt;&lt;p&gt;Main Window:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/pdf-tools/static/pdftools.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/pdf-tools/static/pdftools.th.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Info:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/pdf-tools/static/pdfinfo.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/pdf-tools/static/pdfinfo.th.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Font Info:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/pdf-tools/static/pdffontinfo.png&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/pdf-tools/static/pdffontinfo.th.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;This script requires &lt;a href=&quot;apt:poppler-tools&quot;&gt;poppler-tools&lt;/a&gt; and &lt;a href=&quot;apt:zenity&quot;&gt;zenity&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Copy Script From: &lt;a href=&quot;https://stebalien.com/blog/pdf-tools/static/PDFTools.sh&quot;&gt;PDFTools.sh&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/gnome-blog-patched/</id><title>Gnome-Blog (patched)</title><link href="https://stebalien.com/blog/gnome-blog-patched/" rel="alternate"/><updated>2008-04-18T00:00:00+00:00</updated><published>2008-04-18T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I have just patched gnome-blog (and spent hours tweaking it) and it now
correctly posts titles with blogger. I will post the install
instructions along with the gutsy packages required tomorrow.&lt;/p&gt;&lt;p&gt;P.S.&lt;/p&gt;&lt;p&gt;I am posting this from gnome-blog.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/blogtk-tomboy-blogposter-gnome-blog/</id><title>BloGTK + tomboy-blogposter + gnome-blog</title><link href="https://stebalien.com/blog/blogtk-tomboy-blogposter-gnome-blog/" rel="alternate"/><updated>2008-04-17T00:00:00+00:00</updated><published>2008-04-17T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I have tested &lt;a href=&quot;apt:blogtk&quot;&gt;BloGTK&lt;/a&gt; + &lt;a href=&quot;apt:tomboy-blogposter&quot;&gt;tomboy-blogposter&lt;/a&gt; + &lt;a href=&quot;apt:gnome-blog&quot;&gt;gnome-blog&lt;/a&gt; and all of them have problems with blogger. Gnome-blog is the simplest. It is a panel applet for the gnome-panel and has a very clean, simple, and fast interface. On the other hand, BloGTK is a standalone app that is a little bulky. Neither of these programs will post titles of posts correctly (BloGTK refuses to allow a title at all and gnome-blog posts the title as part of the body of the post).  Tomboy-Blogposter does post the title correctly (because it uses the new API) but does not allow for HTML (links etc…). I am therefore resigned to look for another program (I will try Scribfire next).&lt;/p&gt;&lt;p&gt;Edit: Gnome-Blog cannot post pictures to blogger.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/moving-day/</id><title>Moving Day</title><link href="https://stebalien.com/blog/moving-day/" rel="alternate"/><updated>2008-04-17T00:00:00+00:00</updated><published>2008-04-17T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I just moved to blogger (obviously) because it has comments and many more features than Tumblr. (This move was recommended by &lt;a href=&quot;http://nostalgiaofthemind.wordpress.com/&quot;&gt;Josh Stroud&lt;/a&gt;)&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/2wireiwl3945crash/</id><title>2wire+iwl3945=Crash</title><link href="https://stebalien.com/blog/2wireiwl3945crash/" rel="alternate"/><updated>2008-04-06T00:00:00+00:00</updated><published>2008-04-06T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;It turns out that my wireless problem is in my router not my computer. My 2wire router crashes when I try to connect to it using the &lt;code&gt;iwl3945&lt;/code&gt; driver. Ubuntu Gutsy used the &lt;code&gt;ipw3945&lt;/code&gt; driver (now deprecated). That driver worked. Otherwise, Hardy is cool.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/installing-ubuntu-beta/</id><title>Installing Ubuntu beta</title><link href="https://stebalien.com/blog/installing-ubuntu-beta/" rel="alternate"/><updated>2008-04-03T00:00:00+00:00</updated><published>2008-04-03T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;The wireless card continues to not work but I am installing anyway (nothing to loose). I am hoping that the fix on &lt;a href=&quot;https://bugs.launchpad.net/ubuntu/+bug/139642&quot;&gt;launchpad&lt;/a&gt; will work.&lt;/p&gt;&lt;p&gt;Edit: The battery monitor applet does work in GNOME.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/xubuntu-804-beta-1/</id><title>Xubuntu 8.04 Beta 1</title><link href="https://stebalien.com/blog/xubuntu-804-beta-1/" rel="alternate"/><updated>2008-04-03T00:00:00+00:00</updated><published>2008-04-03T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I just tried out Xubuntu hardy beta with full disk encryption.&lt;/p&gt;&lt;p&gt;Verdict: Broken.&lt;/p&gt;&lt;ol start=&quot;1&quot;&gt;&lt;li&gt;Xubuntu would not properly display my battery percentage (the panel applet would display 28%, 100%, or 0%).&lt;/li&gt;&lt;li&gt;The wireless worked fine and then suddenly stopped working (I had not updated or otherwise messed with my system). When I conncted to a wireless network said network would go down (tested with 2wire only).&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I am now trying ubuntu 8.04 beta x86_64 (the 64 bit version) on my dual core, 64bit Intel laptop. I will post the results.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/turn-on-keyboard-led-when-receiving/</id><title>Turn on keyboard led when receiving mail</title><link href="https://stebalien.com/blog/turn-on-keyboard-led-when-receiving/" rel="alternate"/><updated>2008-03-25T00:00:00+00:00</updated><published>2008-03-25T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;After a lot of fruitless searching I finally figured out the command to &lt;strong&gt;turn the scroll lock led on and off&lt;/strong&gt; in X. I first tried &lt;code&gt;setleds +scroll&lt;/code&gt; and &lt;code&gt;setleds -scroll&lt;/code&gt; but that only works in a Virtual Terminal. I then tried &lt;code&gt;xset led on&lt;/code&gt; and &lt;code&gt;xset led off&lt;/code&gt;. That one messes up the num pad. I finally came up with the following:&lt;/p&gt;&lt;p&gt;On:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;xset led 3
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Off:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;xset -led 3
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I use &lt;a href=&quot;apt:mail-notification&quot;&gt;mail-notification&lt;/a&gt; and set the scroll lock led to go on when I have new mail and set it to go off when I have read the mail.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/my-desktop/</id><title>My Desktop</title><link href="https://stebalien.com/blog/my-desktop/" rel="alternate"/><updated>2008-03-18T00:00:00+00:00</updated><published>2008-03-18T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;After a lot of tweaking, my desktop is beginning to look how I would like it to look.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stebalien.com/blog/my-desktop/static/screenshot.jpg&quot;&gt;&lt;img src=&quot;https://stebalien.com/blog/my-desktop/static/screenshot.th.jpg&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/plugging-friends-blog/</id><title>Plugging a friend's blog</title><link href="https://stebalien.com/blog/plugging-friends-blog/" rel="alternate"/><updated>2008-03-13T00:00:00+00:00</updated><published>2008-03-13T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;This is my friend &lt;a href=&quot;http://nostalgiaofthemind.wordpress.com/&quot;&gt;Josh’s blog&lt;/a&gt;. Random but interesting.&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/xnameorg/</id><title>Xname.org</title><link href="https://stebalien.com/blog/xnameorg/" rel="alternate"/><updated>2008-03-12T00:00:00+00:00</updated><published>2008-03-12T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I just signed up with &lt;a href=&quot;http://www.1and1.com/&quot;&gt;1and1.com&lt;/a&gt; and found that they do not offer marginally advanced DNS settings and allow only five subdomains. To work around these limitations I signed up with &lt;a href=&quot;http://www.xname.org/&quot;&gt;Xname.org&lt;/a&gt;. They allow unfettered customization of the DNS record and are free. I very much recommend them (and thank them).&lt;/p&gt;</content></entry><entry><id>https://stebalien.com/blog/10-hour-fix/</id><title>10 Hour Fix</title><link href="https://stebalien.com/blog/10-hour-fix/" rel="alternate"/><updated>2008-03-09T00:00:00+00:00</updated><published>2008-03-09T00:00:00+00:00</published><content type="html">&lt;blockquote class=&quot;note&quot;&gt;&lt;p&gt;Caveat lector: I wrote this post in high school; it’s likely outdated and poorly written.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Today I spent 10 hrs fixing my hostname.&lt;/p&gt;&lt;p&gt;I had just bought the domain stebalien.com and was fiddling around with my network settings with &lt;strong&gt;network-admin&lt;/strong&gt; and found the domain box. I typed in my new domain to see what the setting did. Nothing happened for a while so I erased the setting and changed the hosts file back to its original state (without the domain name). Later, while I was using Firefox, my session crashed. I logged back in and received this error:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(process:6725): Gtk-WARNING **: This process is currently running setuid or setgid.
This is not a supported use of GTK+. You must create a helper
program instead. For further details, see:

    http://www.gtk.org/setuid.html

Refusing to initialize GTK+.

(process:6729): Gtk-WARNING **: This process is currently running setuid or setgid.
This is not a supported use of GTK+. You must create a helper
program instead. For further details, see:

    http://www.gtk.org/setuid.html

Refusing to initialize GTK+.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I was consequently logged out. This error had nothing to do with the hostname so I assumed that one of my files had been corrupted. After reinstalling half of my system (&lt;code&gt;sudo aptitude reinstall packages&lt;/code&gt;) and looking through all of my configuration files I finally logged in using &lt;code&gt;startx&lt;/code&gt;. I was immediately logged back out with a &lt;code&gt;hostname internal error&lt;/code&gt;. I spent another hour going over my host configuration files (/etc/hosts, /etc/hostname, …) and all of my DNS configuration files. I finally logged back in using the &lt;strong&gt;Failsafe Terminal&lt;/strong&gt;, typed in &lt;code&gt;sudo network-admin&lt;/code&gt; and re-added my domain name, logged back out and in, and my session did not crash. I then proceeded to launch firefox. The page did not load. I could ping, tracerout, and use elinks (a text based web browser for the console) but I Firefox would not load any website. I tinkered with my network-admin settings for 15 minuets before I finally realized that &lt;strong&gt;I had a backup&lt;/strong&gt; of my network-admin settings. I restored my settings and proceeded to write this blog post. I am still clueless as to what my problem was but am happy that it was at least fixed.&lt;/p&gt;</content></entry></feed>