Mozilla/Firefox bookmarks as fvwm menu

I’m neither sure if the place is right nor if this is of any interest. I wrote a Perl script that converts Firefox/Mozilla bookmarks (bookmarks.html) to a fvwm menu. The script is a bit quick and dirty and needs some cleanup and sanity checking, but does recreate the bookmark tree with any favicons included in bookmarks.html

[code]#!/usr/bin/perl

v0.1a - alex pleiner alex-fvwm@zeitform.de

code is GPL - use at own risk

modules

use strict;
use HTML::TreeBuilder;
use MIME::Base64;
use Digest::MD5 qw(md5_hex);
use Image::Magick;

config

my $file = “/home/alex/.mozilla/firefox/xxxxxxx.default/bookmarks.html”;
my $max_length = 40;
my $basemenu = “start”;
my $bookmark_icon = “16x16/bookmark.png”;
my $folder_icon = “16x16/bookmark_folder.png”;
my $menu_prefix = “bookmarks_”;
my $command = “mozilla”;
my $favicon_dir = “/home/alex/.fvwm/icons/tmp”; ## check if exists

variables

my (%menu, %files, %id);
my $c=1;

supported icon types

my %magick = ( “image/png” => “png”, “image/gif” => “gif”, “image/x-icon” => “ico” );

treebuilder

my $tree = HTML::TreeBuilder->new; # empty tree
open(my $fh, “<:utf8”, $file) || die;
$tree->parse_file($fh);

init root menu

$menu{$basemenu} = menustart($basemenu);

iterate

foreach my $f ($tree->descendants)
{
## skip unless folder, item or separator
next unless $f->tag eq “a” or $f->tag eq “hr” or $f->tag eq “h3”;

## parent
my $pid;
if ($f->depth == 4 && defined $f->attr("personal_toolbar_folder"))
  {
    # we might wish to do something different here ....
    # $menu{$basemenu} .= menusubmenu($c, $f->as_text);
    $pid = $basemenu;
  }
elsif ($f->depth == 4)
  {
    $pid = $basemenu;
  }
else
  {
    my $parent = ($f->look_up("_tag", "dt"))[1] || $basemenu;
    $id{$parent} ||= $c++;
    $pid = $id{$parent};
  }

## debug
#print $f->tag, "-", $f->depth, " - ", $f->address, "-", $pid, "-$c-", $f->as_text, "\n";

## bookmarkitem
if ($f->tag eq "a")
  {
    my $filename;

    ## favicon
    if (my $icon = $f->attr("icon"))
      {
        my ($type,$encoding,$data) = ($icon =~ /^data]+?\/[^;]+);(\w+?),(.+)$/);
        #print "found icon $type - $encoding\n";

        ## supported
        if ($encoding eq "base64" && defined $magick{$type})
          {
            my $name = md5_hex($data);

            ## not yet created
            if (! defined $files{$name})
              {
                $files{$name} = sprintf("%s/%s.png", $favicon_dir, $name);

                unless (-r $files{$name})
                  {
                    my $image=Image::Magick->new(magick=>$magick{$type});
                    $image->BlobToImage(decode_base64($data));
                    $image->Resize(geometry=>"16x16");
                    defined $image->[0]
                      ? $image->[0]->Write($files{$name})
                      : $image->Write($files{$name});
                    #$image->Write($files{$name});
                  }
              }
            $filename = $files{$name};
          }
      }

    $menu{$pid} .= menuitem($f->as_text, $f->attr("href"), $filename);
  }

## separator
elsif ($f->tag eq "hr")
  {
    $menu{$pid} .= menusep();
  }

## folder
elsif ($f->tag eq "h3")
  {
    $menu{$pid} .= menusubmenu($c, $f->as_text);
    $menu{$c}    = menustart($c) if ! defined $menu{$c};
  }

}

output

foreach (keys %menu)
{
print $menu{$_}, “\n”;
}

exit;

sub land

sub menusubmenu
{
my ($id, $text) = @_;
return sprintf("+ %%$folder_icon%%"%s" Popup %s%s\n", clean($text), $menu_prefix, $id);
}

sub menustart
{
my ($id) = @_;
return sprintf(“DestroyMenu %s%s\nAddToMenu %s%s\n”, $menu_prefix, $id, $menu_prefix, $id);
}

sub menuitem
{
my ($text, $url, $icon) = @_;
return sprintf("+ %%%s%%"%s" Exec exec %s ‘%s’\n", ($icon||$bookmark_icon), clean($text||$url), $command, $url);
}

sub menusep
{
return “+ “” Nop\n”;
}

sub clean
{
my ($text) = @_;
if (length($text) > $max_length) { $text = substr($text, 0, $max_length) . “…”; }
$text =~ s/*//g; # $text =~ s/*/\*/g;
$text =~ s/\/\\/g;
$text =~ s/&/&&/g;
$text =~ s/"/\"/g;
$text =~ s/’/\’/g;
$text =~ s/$/\$$/g;
$text =~ s/\n/\\n/g;

return $text;

}
[/code]

usage:

  • edit config
  • run from comandline to check
  • add to .fvwm2rc

[code]PipeRead ‘perl $[fvwm_script]/bookmarks-menu.pl’

later

DestroyMenu MenuFvwmRoot
AddToMenu MenuFvwmRoot

other entries

  • %16x16/bookmark_folder.png%“Bookmarks” Popup bookmarks_start
    [/code]

Edit: added Screenshot:

apl

[color=red]Edited by theBlackDragon:
–> Well, Perl isn’t an Fvwm function, so moving to Other languages :slight_smile:[/color]

This is awesome. Can’t wait to try it out. I have been wanting something exactly like this! Yea!

Lol I was yesterday messing around with dinamic menus and thought, amongst other things, of something similar to this… Seems that I can skip some work now (much less pain for me, since I know nothing about perl :smiley: )

Thanks for this, it worked out of the box in my config goes to make/find a bookmark.png :wink:

You might like this, I made it dinamical, so it gets updated (in other words, your script is run) each time you open the menu. This is done thru a function and the MissingSubMenuFunction command, so your bookmarks are always up to date.

DestroyFunc Bookmarks
AddToFunc Bookmarks
+ I DestroyMenu bookmarks_start
+ I AddToMenu bookmarks_start
+ I + DynamicPopDownAction DestroyMenu bookmarks_start
+ I PipeRead 'perl $[fvwm_scripts]/bookmarks-menu.pl'
+ I Popup bookmarks_start

DestroyMenu menu_Root
AddToMenu   menu_Root
+ MissingSubmenuFunction					Bookmarks
........  <whatever>
+ %menu/bookmark.png%"Bookmarks"      	Popup bookmarks_start
..................

Now im playing with the exports in opera, to see if I can make this work under it. Seems that opera only export bookmarks to html in a plain fashion (no tree, go figure what does that mean when you have a +40 nodes tree, with subtrees and lots of bookmarks into it…). If someone is more experienced with this crappy browser and knows anything that I might be missing just let me know :wink:

I thought about that. The favicons are created only once so this doesn’t add load, but the HTML-TreeParser is a resource hug.

No need to export. I’m not an opera expert, but it looks like opera stores “bookmarks” in ~/.opera/opera6.adr and the favicons in ~/.opera/images. Creating a new folder with one bookmark looks like

#FOLDER
        ID=25
        NAME=zeitform
        CREATED=1132827988

#URL
        ID=24
        NAME=Willkommen bei zeitform Internet Dienste - zeitform Internet Dienste
        URL=http://www.zeitform.de/
        CREATED=1132827979
        VISITED=1132827974
        DESCRIPTION=... bla bla ...
        ICONFILE=www.zeitform.de.ico

-

This looks parsable (“-” seems to be the end-of-folder maker).

apl

Yes, I noticed :slight_smile: and dropped the missingsubmenufunction in less than a minute. You are right.

I know all that, but for some reason the menu does not display, I will dig a bit more and see if it is indeed an error parsion the thing or there is any other thing in the middle. Thanks for your response :wink:

EDIT: I think that what confuses the thing is the fact that I have somewhere in the middle a menu with more sublevels. From that afterwards all seems to appear messed and mixed in a weird manner. All the items are there, though…

I have not digged very deep, but I would try something like the following code fragment

### fragment
my $ID   = qr(\bID=([^\n]+)\n);
my $NAME = qr(\bNAME=([^\n]+)\n);
my $URL  = qr(\bURL=([^\n]+)\n);
my $ICON = qr(\bICONFILE=([^\n]+)\n);

$/=""; ## read in paragraph mode

my @ids = qw(start); ## stack for open folders

while(<IN>)
{
  if (/^#FOLDER\b.+$ID.+$NAME/s) ## hopefully opera keeps the order!
    {
      my ($id, $name) = ($1, $2);
      push @ids, $id; ### "open folder"
      $menu{$ids[-1]} = menustart($ids[-1]); ### start menu with last id
      $menu{$ids[-2]} = menusubmenu($ids[-1], $name); ### add to last-but-one menu
    } 
  elsif (/^#URL\b.+$NAME.+$URL.+$ICON/s)
    {
      my ($name,$url, $icon) = ($1, $2, $3);
      # generate icon
      $menu{$ids[-1]} = menuitem($name, $url, $icon); ## add to last menu
    }
 elsif (/^-$/) 
    {
      pop @ids; ### "close folder"
    }
}

apl
edit: added regexes

Thanks, it works, at least for now :wink: