Friday, March 12, 2021

Importing mail from Google Takeout into another Gmail account

I had a Google Takeout export of my old University Google account, which I wanted to import into my personal Gmail. Unfortunately, Google does not provide an easy way to do this. I decided to use this as an opportunity to refresh my Haskell language skills, and in particular, to familiarize myself with the Pipes library. The idea was to process an arbitrary size mailbox in fixed memory without explicitly relying on Haskell's laziness. I will not bother you with details of my Haskell implementation. You can take a look at the code yourself, but take it with a grain of salt: I am not a practicing Haskell programmer. For me, this language is more of a lingua franca used in the academic functional programming community. I want to tell you about a few quirks and bugs of the Gmail IMAP interface I've discovered.

First of all, Google Takeout conveniently exports all your mail in standard mbox format as one huge file. Interestingly, this file also contains your chat transcripts, disguised as RFC822-formatted messages, but with the Message-Id field. They could be distinguished by the presence of the "Chat" label in the X-GM-Labels header field. I chose to discard them.

Speaking of labels: IMAP has folders while Gmail has labels. Gmail IMAP interface maps labels to folders. I use IMAP's STORE command and Gmail X-GM-LABELS IMAP extension to assign multiple labels to a single message.

Standard IMAP protocol (without UIDPLUS extension) does not give you a unique ID of a message you just appended. I need this unique ID to assign labels. So after successful message addition, I immediately look it up by Message-Id header field. At this point, I hit two bugs in Gmail IMAP implementation:

Bug #1

When Message-Id contains '%' character, the IMAP SEARCH command fails to find it. This a screenshot of this message present in my mailbox taken from the Gmail web interface:



But the following IMAP command fails to locate it:
UID SEARCH HEADER Message-ID "<D7DA993B.B30DC%psztxa@exmail.nottingham.ac.uk>"
Some people observed the same problem with "!" character and speculated that Gmail split message-id into "words" before indexing. The workaround proposed simulates this split and performs a search on the conjunction of several parts of the subject and then verifies that the found message has the correct Message-Id by fetching it. Instead, I chose to implement a more lightweight solution using X-GM-RAW Gmail's extension to IMAP's SEARCH command, which allows searching using google search syntax. In particular, for the example used above, one can use 
 
"Rfc822msgid:<D7DA993B.B30DC%psztxa@exmail.nottingham.ac.uk>" 
 
search query, which successfully finds the message with "%" in Message-Id.

Bug #2

Another Message-Id-related bug I've stumbled upon is more surprising. It turns out, when indexing messages, Gmail strips square brackets from Message-Ids! If you look at the screenshot from the web interface, you can see that Message-Id shown in the header does not match the actual field value below! To search for such messages, I had to strip square brackets in the search string.


Conclusion

This exercise is not a production-ready script, and some other problems may occur during the import, but I was able to successfully import my mailbox with about 32K messages. You are welcome to hack my script for your own needs. I've submitted a pull request for HaskellNet library with my bugfixes and changes to support Gmail IMAP extensions.
 
P.S. Both bugs were reported to Google: #183687621 and #183677218.




Tuesday, October 20, 2020

My computer setup

In this post, I will share how my 2020 computer setup looks (hardware and software). It will be interesting to read this in a few years to see how things may have changed.

Hardware:

I do 100% of my work on my laptop without using an external keyboard, monitor, or mouse. This gives me maximum portability. I can work anywhere: in a cafe, on an airplane, at the desk or on a sofa. My laptop is DELL XPS-13, which is very slim and lightweight. It is great for travel, and I have it in my backpack pretty much wherever I go. Performance-wise (I am doing a lot of compiling), it is pretty decent with i7 CPU, 16Gb of RAM, and SSD. Display quality is important to me, and Dell's 4920x2160 built-in HDPI display is very sharp and bright. I am not very happy with the keyboard and the touchpad, but I can live with it. The only external hardware I occasionally use is a 23-inch external monitor in portrait mode. I use it for convenience when I work on papers to preview my writing, showing a full page of LaTeX-generated PDF.

Software:

I am using the Ubuntu LTS release. I know there are cooler/more open Linux distros, but I need to balance tweaking my OS and getting actual work done. My hard drive is encrypted, in case my laptop is lost of stolen.

I am using `i3` window manager. It took me some time to customize it the way I want it to behave, but since I've switched to it was the most significant recent software-related productivity improvement. Virtual screens, keyboard shortcuts, efficient use of screen real estate allows me to be very productive.

I am using a `fish` shell. I like its interactive features and nice look-and-feel I have carved for myself using `omf` packages. However, I do not use it for scripting. In rare cases, when I need to do shell scripting, I use `bash.`

For almost three decades, my text editor is `emacs.` I am using many emacs packages, but if I have to name a few which I use the most, they are `helm,` `magit,` and `org-mode.` I use a solarized dark color theme in emacs, shell, and window manager.

My web browser is Firefox. Among a few extensions I use, `LastPass` is worth  mentioning.

Media processing:

At my home office I have a separate Mac Mini with 28-Inch 4k UHD Monitor, which I use solely for photo and video processing and video conferencing (due to "nose cam" on my Dell Laptop). The media processing software I use is Adobe Lightroom (standalone version) and Apple iMovie.

P.S. I may write a follow-up post about online services I use.



Wednesday, February 6, 2019

Switching between 3 languages with i3

I type in 3 languages: English, Ukrainian, and Russian. It turns out, I never have to switch directly between Russian and Ukrainian. However, when typing in either Russian or Ukrainian I often switch to English to type email addresses, URLs, Unix commands, etc. In such scenario using standard X11 keyboard switching mechanism with setxkbmap specifying all 3 languages is impractical. It cycles through them sequentially! For comparison, MacOS handles this situation nicely with cycling only through the last 2 most recently used keyboard layouts. I already posted a solution for standard Ubuntu window manager, but now I wanted to make it work with i3 window manager.

The idea is to have two typing models "us/ru" and "us/ua". You can switch between them using a key combination. Within each mode, you can switch between the languages using Alt+SPACE (or another key).



First, download this script and copy it into your ~/bin/ directory. Then add the following 3 lines to your ~/.config/i3/config:

bindsym Mod1+space exec "xkb-switch -n; pkill -x --signal=SIGUSR1 i3status"

exec_always "setxkbmap -layout us,ru -variant ,mac"

bindsym $mod+z exec "~/bin/cycle.sh /dev/shm/kbd2.txt 'setxkbmap -layout us,ru -variant ,mac' 'setxkbmap -layout us,ua' ; pkill -x --signal=SIGUSR1 i3status"

This is assuming you are using i3status keyboard indication method described in my previous post. Using it will also get the instant status bar update of the current keyboard languages. If you do not use i3status or do not need keyboard indicator you can use the following 2 lines instead:

exec_always "setxkbmap -layout us,ru -option grp:alt_space_toggle 

bindsym $mod+z exec "~/bin/cycle.sh /dev/shm/kbd2.txt 'setxkbmap -layout us,ru -variant ,mac -option grp:alt_space_toggle' 'setxkbmap -layout us,ua -option grp:alt_space_toggle'

Now you can use WindowsKey+Z to switch between "us/ua" and "us/ru" modes and Alt+SPACE to switch between en and either ru or ua layouts.


Keyboard layout indicator for i3 with i3status

I am using i3 window manager with i3status bar. This post describes how to set up a responsive keyboard layout indicator if you are using multiple keyboard layouts. It looks like this:


In the screenshot above it shows that I have 2 keyboard layouts: American English (us) and Ukrainian (ua) and currently the first one is active.

Display part is implemented by this script. It depends on xkblayout-state utility. Save this script and compiled version of xkblayout-state to your ~/bin/ directory.

In your ~/.config/i3status/config add "output_format = i3bar" line to general section:

general {
        colors = true
        interval = 5
        output_format = i3bar        
}

In ~/.config/i3/config add:

bar {
    status_command ~/bin/i3status.sh
    output primary
    tray_output primary
}

and

exec_always "setxkbmap -layout us,ua -option 'grp:alt_space_toggle'"

This will allow you to switch keyboard with Alt+SPACE, and the indicator will display the current status.

So far, so good, but the problem is that the status bar is updated every 5 seconds. We would want keyboard layout indicator to update immediately. This could be done. It will require another command line utility: xkb-switch (available via apt on Ubuntu Linux). The idea is to implement switching via i3 hotkey, which will also trigger an instant status bar update. Here is  a section of i3 config:

exec_always "setxkbmap -layout us,ua"

bindsym Mod1+space exec "xkb-switch -n; pkill -x --signal=SIGUSR1 i3status"

We removed ALT+SPACE toggle option from setxkbmap, and implemented it as bindsym. Sending USR1 signal to i3status triggers the immediate update.


Thursday, January 31, 2019

Dell XPS-13 Touchpad problem (analysis and solution)

I recently switched to DELL XPS 13 as my primary laptop. In addition slick design and modern specs, the extra bonus is that there is good Linux support thanks to Dell's Project Sputnik.

However, I quickly discovered that it is plagued by a common problem of random touchpad clicks. It is reported by numerous users and various workarounds were proposed. It exhibits as follows: while I type my touchpad randomly detects a click and my cursor jumps to a random position in the text while I continue typing. Pressing Ctrl-Z and re-typing from the last good position becomes a reflex you exercise a hundred times a day. It is so annoying I was considering going back to my old laptop.

I never had this problem on my previous laptop (Lenovo X260) or on several MacBook's I've owned before that. Both Lenovo and DELL are using Synaptic touchpad with the same driver. I decided to investigate.

I am right-handed and I while typing sometimes I reach with my right hand to left side of the keyboard. For example, to type $ sign, I hold left SHIFT button with my left thumb and reach to $ with my right hand's index finger. Doing this puts my thumb above the touchpad which in my theory could be causing an erroneous click. Perhaps this is not a proper typing technique but it is too late for me to change my habits.

Now, let us look at touchpad location for both laptops:

Lenovo X260
Dell XPS 13

And superimpose their respective touchpad/keyboard layouts:


As you can see Lenovo's touchpad is narrower and positioned to the left off center. Also, it has two physical buttons on the top, which I never use, but they put active touchpad area further from the spacebar vertically. Kudo's their ergonomic team!

So, how do I fix this? We can use synclient(1) utility to adjust the size of DELL's touchpad active area. First, the geometry of the touchpad could be queried with the following command (the values may be different on our computer):

$ synclient -l                                        
Parameter settings:
    LeftEdge                = 48
    RightEdge               = 1168
    TopEdge                 = 36
    BottomEdge              = 644
...

The origin of the coordinate system (0,0) is in the upper right corner with X-axis extending to the right and Y away from the screen. Now we can move the right and bottom edges a bit with the following commands:

$ synclient AreaRightEdge=1000
$ synclient AreaTopEdge=130  
$ synclient PalmDetect=1

We've moved the right edge from 1168 to 1000, cutting off a vertical stripe 1168 pixels wide. Also, we've moved the top from 36 to 130, cutting off the horizontal strip 94 pixels high. We've also enabled palm detection, for the cases where you still manage to touch the touchpad with part of your palm. The changes to the geometry are shown below. The part shown in red is no longer sensitive to touches. 



These numbers work for me but you may need to adjust for your liking. It does not fully eliminate the problem but certainly makes things better, at least for me.

P.S. There is a known issue with 2 touchpad drivers activated at the same time. From this forum post:

If command `xinput --list` shows something like this:

⎡ Virtual core pointer  id=2 [master pointer (3)]
⎜   ↳ Virtual core XTEST pointer  id=4 [slave pointer (2)]
⎜   ↳ ELAN24F0:00 04F3:24F0  id=12 [slave pointer (2)]
⎜   ↳ DELL07E6:00 06CB:76AF Touchpad  id=13 [slave pointer (2)]
⎜   ↳ SynPS/2 Synaptics TouchPad id=14    [slave pointer (2)]
...

   Which includes two touchpad devices, one needs to be disabled. Use following commands:

   cd /etc/X11
   sudo mkdir xorg.conf.d
   cd xorg.conf.d/
   sudo vi 51-synaptics.conf

Then type:

   Section "InputClass"
   Identifier "touchpad ignore SynPS/2 Synaptics duplicate"
   MatchProduct "SynPS/2 Synaptics TouchPad" 
   Option "Ignore" "on" 
   EndSection
   
   Then, restart X11.




Saturday, November 3, 2018

The brave new world of computational photography, or why cell phones are killing SLRs

Until very recently photography, for decades, has not endured much change. Most changes to film emulsions, lens optics, and mechanics were more quantitive than qualitative.  Even the first switch to digital which amounted to slapping a digital sensor instead of the film was not revolutionary.

The art of photography is of translating the image of the real world to a still picture on an arbitrary media (paper, screen, slide). It was never about fidelity as the precise replication of that is seen by the human eye is physically impossible. The "art" part is how the picture is transformed. The arsenal of effects of traditional photography was limited: depth of field, focal distance, movement blur, color curves, vignetting, and various optical distortions like chromatic aberrations and results of using optical filters, to name a few. The post-processing steps add a few more, but let us put these aside for now.

As a photographer, I've built a mental model of the camera system, and learned to estimate how changing its parameters will affect the image I am taking. Again, fundamentally they are very few: focus, sensor ISO sensitivity, focal length (zoom), aperture, and exposure. For more specialized cases you may want to know about the type of shutter (running or circular) and your optical filters properties. Most cameras, from $30 point-and-shoot to $3,000 SLR just help you to manage these very same parameters.

The early generations of mobile phone cameras worked exactly like that. On the top of the line (at the time of this writing) Samsung phone, Galaxy S9, you can switch to "Pro" mode and tweak focus, exposure, aperture, sensitivity as some now long dead photographer did on his TLR camera 130 years ago!

But unlike cameras, modern phones have formidable computational resources. They can process data from camera sensor in real-time using their fast CPUs and GPUs. They can do it while taking a picture. Suddenly techniques like using imperceptible hand motion to get several slightly offset images from which a higher-resolution image could be reconstructed [1] become possible. Another burst technique, HDR, used to achieve the better dynamic range become so standard that it is enabled by default on Galaxy S9 and Google Pixel 3. The camera sensors are also evolving. Companies like Lytro developed sensors which capture information about the direction that the light rays are traveling in space. Apple first pioneered phones with dual cameras which opens a world of possibilities. Now you can take simultaneously two pictures at different focal lengths, with different parameters and combine information from them to construct a composite image. This could also be combined with burst techniques. Finally, as the two cameras a physically apart and giving two distinct vantage points, one can try to reconstruct some of the 3D features of the scene.

Comparing digital cameras become more difficult. Before you look at sensor resolution (megapixels), dynamic range, color response curves, focal length, and range of apertures and exposures as the starting point to compare 2 cameras. This is no longer true. Your phones now have multiple cameras, taking multiple sensor readings for each picture, controlled by complex AI-driven proprietary algorithms, which are sometimes as smart as detecting the types of multiple objects in your viewfinder and choosing the best way to render them in a shot.

My Google Pixel 3 camera does not have a "Pro" mode. And this is not because they chose to "dumb it down" for the consumer. The reason is that the set of controls we used to is no longer applicable. The world has changed and many of the skills I've learned over years as a photographer no longer apply. Finally, the real disruption to the camera industry has arrived.

Thursday, March 9, 2017

Writer comonad in Coq

In the last post, we reviewed Writer comonad in Haskell. In this post we will implement it in Coq. Since monads are not the part of standard Coq library, and we will use ExtLib which provide basic monad definitions.

First, we need to define Writer monad. ExtLib only provides WriterT monad transformer, from which we can trivially create a Writer monad:

Variable s t : Type.
Variable Monoid_s : Monoid s.

Definition writer := writerT Monoid_s ident.
Definition runWriter x := 
   unIdent (@runWriterT s Monoid_s ident t x).
Definition execWriter x:= psnd (runWriter x).
Definition evalWriter x:= pfst (runWriter x).

Here, we define `writer` type constructor and `runWriter` method.  Additionally, we define a couple of convenience methods on top of `runWriter` to extract state and the value from the results of unwrapping the monad.

ExtLib has CoMonad class defined as:

Class CoMonad (m : Type -> Type) : Type :=
{ coret : forall {A}, m A -> A
; cobind : forall {A B}, m A -> (m A -> B) -> m B
}.

The first thing you notice is that the names are different from the ones we've seen in Haskell. They reflect category theory duality between monads and comonads. Essentially 'coret' is 'extract,' and 'cobind' is 'extend' with slightly different argument order. For now, let us stick to these definitions. We define a Writer comonad as:

Variable w: Type.
Variable m: Monoid w.

Global Instance WriterCoMonad:  CoMonad (@writer w m) := {
  coret A x := evalWriter x ;
  cobind A B wa f := tell (execWriter wa) ;; ret (f wa)
}.

The definition is pretty much the same as we wrote in Haskell except for the adjustments for Coq/ExtLib monad syntax.

Since we are in Coq, in addition to defining the comonad we can formalize comonad laws and prove that our implementation complies with them. Unfortunately, `cobind` argument's order make the formulation of comonad rules little clumsy, so we will first define wrappers reverting it to familiar Haskell's name and argument order:

Variable m : Type -> Type.
Variable C : CoMonad m.

Definition extract {A:Type}: (m A) -> A := @coret m C A.
Definition extend {A B:Type} (f: m A -> B): m A -> m B  :=
    fun x => @cobind m C A B x f.

Now we can define CoMonadLaws class which specifies the rules:

Class CoMonadLaws : Type := {
  extend_extract: forall (A B:Type),   extend (B:=A) extract = id ;
  extract_extend: forall (A B:Type) {f},   extract  (@extend A B) f = f;
  extend_extend: forall (A B:Type) {f g}, (@extend B A) f  (@extend A B) g = extend (f  extend g)
}.

Finally, we will prove that our WriterComonad satisfies these rules by instantiating this class and proving the rules. To do this, we need to assume that the proof is provided that underlying monid instance used in our Writer complies to MonoidLaws.

Variable ml: MonoidLaws m.

Global Instance WriterCoMonadLaws:
    CoMonadLaws (@WriterCoMonad).
  Proof.
    split.
    -
      intros A B.
      unfold extract, extend.
      extensionality x.
      unfold coret, cobind, WriterCoMonad, id.
      unfold evalWriter, execWriter, runWriter.
      destruct x, runWriterT, unIdent.
      simpl.
      repeat f_equal.
      apply ml.
    -
      reflexivity.
    -
      intros A B f g.
      unfold extract, extend, compose.
      unfold coret, cobind, WriterCoMonad.
      extensionality x.
      repeat f_equal.
      apply ml.
  Qed.

For clarity in the code snippets above, I have omitted some auxiliary code. You can look at the full example at WriterComonad.v.