packages feed

hawitter 0.2 → 0.3

raw patch · 9 files changed

+448/−217 lines, 9 filesdep +mtldep +parsecsetup-changed

Dependencies added: mtl, parsec

Files

Main.hs view
@@ -1,10 +1,14 @@+-- | Reference: <http://apiwiki.twitter.com/Twitter-API-Documentation> module Main where import Control.Monad+import Control.Monad.Trans import qualified Codec.Binary.UTF8.String as U-import Data.IORef+import Data.Char import Data.List import Data.Maybe import Data.Ratio+import Data.IORef+import Data.Version import Data.Time.Clock import Data.Time.Format import qualified Data.Map as M@@ -13,50 +17,118 @@ import Network.URI import Network.HTTP import Network.HTTP.Auth-import Text.JSON-import Text.Regex import System.IO import System.Locale import System.Gnome.GConf+import Text.JSON+import Text.Regex+import Text.Parsec.String+import Text.Parsec hiding (Error,Ok) import qualified Paths_hawitter +consumerKey="lCHiDjvMKkqHIGYTON3Ecw"+consumerSecret="R10OHGwq6XKzXCshkiO4aVWjiKVcUg4DVfPTllIco"  main=do     initGUI     -    si<-statusIconNewFromFile =<< Paths_hawitter.getDataFileName "hawitter_128sq.svg"-    statusIconSetVisible si True-    +    -- construct GUI from glade file and extract pointers to some widget     gladeFile<-Paths_hawitter.getDataFileName "hawitter.glade"     Just gxml<-xmlNewWithRootAndDomain gladeFile (Just "mainwindow") Nothing-    window<-xmlGetWidget gxml castToWindow "mainwindow" +    window<-xmlGetWidget gxml castToWindow "mainwindow"+    men<-xmlGetWidget gxml castToEntry "modifierentry" +    swt<-xmlGetWidget gxml castToViewport "vptimeline"          (mv,showTweets)<-newMessageView-        -    swt<-xmlGetWidget gxml castToViewport "vptimeline"     set swt [containerChild:=mv]     +    let toggleVisibility=do vis<-get window widgetVisible+                            (if vis then widgetHideAll else widgetShowAll) window+    +    -- initialize global state (TODO: make it pure)     pTweets<-newIORef []     pLastId<-newIORef Nothing-    pImageCache<-newIORef emptyImageCache-    let updateTL=update showTweets pTweets pLastId pImageCache+    pIconCache<-mkIconCache     -    -- handlers+    -- setup status icon+    iconN<-pixbufNewFromFile =<< Paths_hawitter.getDataFileName "hawitter.svg"+    iconB<-pixbufNewFromFile =<< Paths_hawitter.getDataFileName "hawitter_busy.svg"+    +    si<-statusIconNewFromPixbuf iconN++    let  -- apply filter here? -> no. more general framework needed.+        addTL delta ts=(length d',d'++ts)+            where d'=(reverse $ sort delta) \\ ts+        insertTL raise ts=do+            t0<-readIORef pTweets+            let (n,t1)=addTL ts t0+            writeIORef pTweets t1+            +            when (n>0) $ do+                showTweets t1+                visible<-get window widgetVisible+                when (raise && not visible) $ statusIconSetFromPixbuf si iconB+        updateTL raise=fetchTL pLastId pIconCache >>= insertTL raise++    -- register handlers     let onAction name f=xmlGetWidget gxml castToMenuItem name >>= flip onActivateLeaf f          onAction "file-account" newAccountDialog     onAction "file-quit" mainQuit-    onAction "timeline-post" newPostWindow-    onAction "timeline-refresh" updateTL+    onAction "timeline-post" $ newPostWindow >>= maybe (return ())+        (\x->insertTL False . (:[]) . snd =<< parseTweet pIconCache x)+    onAction "timeline-refresh" $ updateTL True     onAction "help-about" showAboutDialog-    onDestroy window mainQuit-    timeoutAdd (updateTL >> return True) (60*1000)     -    updateTL-    widgetShowAll window+    on window deleteEvent $ liftIO $ widgetHideAll window >> return True+    timeoutAdd (updateTL True >> return True) (60*1000)+    +    on si statusIconActivate $ statusIconSetFromPixbuf si iconN >> toggleVisibility+    timeoutAdd (statusIconSetVisible si True >> return True) (1*1000)+    +    -- end initialization and enter idle state+    updateTL True     mainGUI --- [newest..oldest]++{-+data MExpr+    =MAdd MExpr MExpr -- plus+    |MSub MExpr MExpr -- minus+    |MAnd MExpr MExpr -- and+    |MXor MExpr MExpr -- hat+    |Hash String+    |Gen UExpr++data UExpr+    =Primitive String+    |Follower UExpr+    |Followee UExpr++parseMExpr :: Parser MExpr+parseMExpr=between (char '(') (char ')') parseMExpr <|> parseHash <|> parseMArith++parseMArith :: Parser MExpr+parseMArith=do+    x<-parseMExpr+    op<-oneOf (map fst table)+    y<-parseMExpr+    return $! (fromJust $ lookup op table) x y+    where table=[('+',MAdd),('-',MSub),('&',MAnd),('^',MXor)]+    ++parseHash :: Parser MExpr+parseHash=do+    x<-char '#'+    xs<-many1 $ satisfy (\x->isAlphaNum x||x=='_')+    return $ Hash $ x:xs+-}+++++-- | Create message view 'Widget' and return it with updater.+-- 'Tweet's should be in descending order. newMessageView :: IO (Widget,[Tweet] -> IO ()) newMessageView=do     vb<-vBoxNew False 2@@ -87,6 +159,7 @@     entryGetText ps >>= gconfSet gconf "/apps/hawitter/basic/pswd" . GConfValueString     putStrLn "gconf configuration complete" +newPostWindow :: IO (Maybe JSValue) newPostWindow=do     gladeFile<-Paths_hawitter.getDataFileName "hawitter.glade"     Just gxml<-xmlNewWithRootAndDomain gladeFile (Just "postdialog") Nothing@@ -97,20 +170,18 @@     buf<-textViewGetBuffer tv     onBufferChanged buf $ updateTweetInfo buf lb     -         rid<-dialogRun d     -    -    when (rid==ResponseUser 0) $ do-        st<-textBufferGetStartIter buf-        en<-textBufferGetEndIter buf-        txt<-textBufferGetText buf st en False-        -        let txtU8=U.utf8Encode txt-        callJSON POST "/statuses/update" [("status",txtU8)]-        return ()+    tw<-if rid==ResponseUser 0+        then do st<-textBufferGetStartIter buf+                en<-textBufferGetEndIter buf+                txt<-textBufferGetText buf st en False+                callJSON POST "/statuses/update" [("status",U.utf8Encode txt)]+        else return Nothing     widgetDestroy d+    return tw + updateTweetInfo buf lb=do     st<-textBufferGetStartIter buf     en<-textBufferGetEndIter buf@@ -121,10 +192,10 @@ showAboutDialog=do     d<-aboutDialogNew     aboutDialogSetName d "hawitter"-    aboutDialogSetVersion d "0.2"+    aboutDialogSetVersion d $ showVersion Paths_hawitter.version     aboutDialogSetComments d "Hawitter is a twitter client for GTK, written in Haskell."     aboutDialogSetAuthors d ["xanxys <xanxys@gmail.com>"]-    aboutDialogSetLogo d . Just =<< pixbufNewFromFile =<< Paths_hawitter.getDataFileName "hawitter_128sq.svg"+    aboutDialogSetLogo d . Just =<< pixbufNewFromFile =<< Paths_hawitter.getDataFileName "hawitter_128.svg"     dialogRun d     widgetDestroy d @@ -135,12 +206,12 @@ markupMessage :: String -> String markupMessage=modifyWithRegex ruser mkUser . modifyWithRegex rhash mkHash . modifyWithRegex rurl mkLink     where-        rurl=mkRegex "http://[-a-zA-Z0-9_./#]+"-        rhash=mkRegex "#[-a-zA-Z0-9_.]+"-        ruser=mkRegex "@[-a-zA-Z0-9_.]+"+        rurl=mkRegex "http://[-a-zA-Z0-9_./#?&=]+"+        rhash=mkRegex "#[a-zA-Z0-9_]+"+        ruser=mkRegex "@[a-zA-Z0-9_]+"                  mkLink  url="<a href=\""++url++"\"><span underline=\"none\">"++url++"</span></a>"-        mkHash hash="<span color=\"brown\">"++hash++"</span>"+        mkHash hash="<span color=\"chocolate\">"++hash++"</span>"         mkUser user="<span color=\"aquamarine\">"++user++"</span>"  modifyWithRegex rx f s=case matchRegexAll rx s of@@ -148,8 +219,9 @@     Just (pre,target,post,_) -> pre++f target++modifyWithRegex rx f post  +-- | Convert 'Tweet' to 'Widget' using 'IconCache'. allocTweet :: Tweet -> IO Widget-allocTweet (Tweet icon (User id) (Source src) date msg)=do+allocTweet (Tweet user@(User icon id _) (Source srcN srcU) date msg)=do     t0<-getCurrentTime          im<-imageNewFromPixbuf icon@@ -157,21 +229,15 @@          -- begin vbCont     lbUser<-labelNew Nothing-    let-        (cname,curl)=extractClientName src-        fromLink-            |null curl = "<span color=\"lightblue\" underline=\"none\">"++cname++"</span>"-            |otherwise = "<a href=\""++curl++"\"><span color=\"lightblue\" underline=\"none\">"++cname++"</span></a>"+    let fromLink=case srcU of+                     Nothing -> "<span color=\"lightblue\" underline=\"none\">"++srcN++"</span>"+                     Just  u -> "<a href=\""++u++"\"><span color=\"lightblue\" underline=\"none\">"++srcN++"</span></a>"     labelSetMarkup lbUser $ unwords ["<b>"++id++"</b>","-",showPastTime t0 date,"-","from",fromLink]-    labelSetLineWrap lbUser False-    labelSetSingleLineMode lbUser True-    set lbUser [miscXalign:=0]+    set lbUser [labelLineWrap:=False,labelSingleLineMode:=True,miscXalign:=0]          lbMsg<-labelNew Nothing     labelSetMarkup lbMsg $ markupMessage msg-    labelSetLineWrap lbMsg True-    labelSetSingleLineMode lbMsg False-    set lbMsg [miscXalign:=0]+    set lbMsg [labelLineWrap:=True,labelSingleLineMode:=False,labelSelectable:=True,miscXalign:=0]          vbCont<-vBoxNew False 1     boxPackStart vbCont lbUser PackNatural 0@@ -184,22 +250,8 @@     return $ castToWidget hbTweet  --- example:--- <a href="http://example.com" rel="nofollow">TwitterClient</a>--- ->--- ("TwitterClient","http://example.com")-extractClientName :: String -> (String,String)-extractClientName s=case matchRegex rx s of-    Just [u,n] -> (escapeBRs n,escapeBRs u)-    _          -> (escapeBRs s,"")-    where-        rx=mkRegex "href=\"([^\"]+)[^>]*>([^<]+)<"-        escapeBRs=concatMap f-        f '<'="&lt;"-        f '>'="&gt;"-        f x=[x]-     + showPastTime now past     |ss<60     = show ss++" seconds before"     |ms<60     = show ms++" minutes before"@@ -210,53 +262,106 @@         ms=ss `div` 60         hs=ms `div` 60 -data Tweet=Tweet Pixbuf User Source UTCTime String -newtype Source=Source String deriving(Show,Eq,Ord)-newtype User=User String deriving(Show,Eq,Ord)+-- | single tweet+data Tweet=Tweet User Source UTCTime String +instance Eq Tweet where+    (Tweet u0 _ d0 msg0)==(Tweet u1 _ d1 msg1)=(d0,u0,msg0)==(d1,u1,msg1) -update showTweets ts li ic=do+instance Ord Tweet where+    compare (Tweet u0 _ d0 msg0) (Tweet u1 _ d1 msg1)=compare (d0,u0,msg0) (d1,u1,msg1)++-- | client name and its URL+data Source=Source String (Maybe String) deriving(Show,Eq,Ord)++-- | icon, id and name+data User=User Pixbuf String String++instance Show User where+    show (User _ id name)=id++" | "++name++instance Eq User where+    (User _ i0 _)==(User _ i1 _)=i0==i1++instance Ord User where+    compare (User _ i0 _) (User _ i1 _)=compare i0 i1+++++++escapeBracket :: String -> String+escapeBracket=concatMap f+    where+        f '<'="&lt;"+        f '>'="&gt;"+        f x=[x]+++fetchTL :: IORef (Maybe Int) -> IconCache -> IO [Tweet]+fetchTL li ic=do     last_id<-readIORef li     resp<-callJSON GET "/statuses/home_timeline" (maybe [] (\x->[("since_id",show x)]) last_id)-    case resp of-        Nothing -> print "couldn't fetch TL"-        Just rb -> updateWithTL showTweets ts li ic rb+    xs<-case resp of+            Nothing -> print "couldn't fetch TL" >> return []+            Just (JSArray ss) -> mapM (parseTweet ic) ss+    let (ids,ts)=unzip xs+    unless (null ids) $ writeIORef li $ Just $ maximum ids+    return ts +-- | parse /source/ parameter+parseSource :: String -> Source+parseSource s=case matchRegex rx s of+    Just [u,n] -> Source (escapeBracket n) $ Just $ escapeBracket u+    _          -> Source (escapeBracket s) Nothing+    where+        rx=mkRegex "href=\"([^\"]+)\"[^>]*>([^<]+)<" -updateWithTL showTweets ts li ic (JSArray ss)=do-    unless (null ids) $ writeIORef li $ Just $ maximum ids+-- | Use 'IconCache' to create 'User' from 'JSValue'.+parseTweet :: IconCache -> JSValue -> IO (Int,Tweet)+parseTweet ic x=do+    user<-parseUser ic $ indexJSA "user" x+    return (id,Tweet user source date message)+    where+        id=fromJSI $ indexJSA "id" x+        source=parseSource $ fromJSS $ indexJSA "source" x+        date=parseDate $ fromJSS $ indexJSA "created_at" x+        message=fromJSS $ indexJSA "text" x -    imgCache<-updateImageCache (zip users icons) =<< readIORef ic-    writeIORef ic imgCache-    -    let tweets=zipWith4 (\u m d s->Tweet (imgCache M.! u) u s d m) users msgs dates sources-    modifyIORef ts (tweets++)-    -    readIORef ts >>= showTweets+-- | Use 'IconCache' to create 'User' from 'JSValue'.+parseUser :: IconCache -> JSValue -> IO User+parseUser ic x=do+    icon<-fetchIconCache ic iconURL+    return $ User icon userId userName     where-        ids=map (fromJSI . indexJSA "id") ss-        msgs=map (fromJSS . indexJSA "text") ss-        dates=map (readDate . fromJSS . indexJSA "created_at") ss-        users=map (User . fromJSS . indexJSA "screen_name" . indexJSA "user") ss-        icons=map (fromJSS . indexJSA "profile_image_url" . indexJSA "user") ss-        sources=map (Source . fromJSS . indexJSA "source") ss-        +        iconURL =fromJSS $ indexJSA "profile_image_url" x+        userId  =fromJSS $ indexJSA "screen_name" x+        userName=fromJSS $ indexJSA "name" x  -- example: "Wed Nov 18 18:54:12 +0000 2009"-readDate :: String -> UTCTime-readDate=readTime defaultTimeLocale "%a %b %e %H:%M:%S %Z %Y"+parseDate :: String -> UTCTime+parseDate=readTime defaultTimeLocale "%a %b %e %H:%M:%S %Z %Y"  -emptyImageCache=M.empty -updateImageCache :: [(User,String)] -> M.Map User Pixbuf -> IO (M.Map User Pixbuf)-updateImageCache uss m=do-    imgs<-mapM pixbufNewFromURL $ map snd nus-    return $! M.fromList (zip (map fst nus) imgs) `M.union` m-    where nus=filter (flip M.notMember m . fst) $ nubBy (\x y->fst x==fst y) uss+type IconCache=IORef (M.Map String Pixbuf) +mkIconCache :: IO IconCache+mkIconCache=newIORef M.empty +fetchIconCache :: IconCache -> String -> IO Pixbuf+fetchIconCache c url=do+    c1<-insertIconCache url =<< readIORef c+    writeIORef c c1+    return $ c1 M.! url++insertIconCache :: String -> M.Map String Pixbuf -> IO (M.Map String Pixbuf)+insertIconCache url m+    |M.member url m = return m+    |otherwise      = liftM (\x->M.insert url x m) $ pixbufNewFromURL url + pixbufNewFromURL :: String -> IO Pixbuf pixbufNewFromURL url=do     r<-simpleHTTP (getRequest url)@@ -266,7 +371,7 @@             (path,h)<-openBinaryTempFile "/tmp" "hawitter"             hPutStr h $ rspBody x             hFlush h-            n<-pixbufNewFromFile path+            n<-pixbufNewFromFileAtSize path 48 48             hClose h             return n     
README.en view
@@ -1,7 +1,7 @@-Hawitter 0.2+Hawitter 0.3  == Installation ==-It should be possible to run 'cabal install hawitter'.+It should be possible to install by running 'cabal install hawitter'.   == Technical Notes ==@@ -14,3 +14,4 @@  * email: xanxys at gmail dot com * twitter: @xanxys_+
README.ja view
@@ -1,4 +1,4 @@-Hawitter 0.2+Hawitter 0.3  == インストール == 'cabal install hawitter'で動くはず。動かない場合は連絡を。@@ -13,4 +13,5 @@  * email: xanxys at gmail dot com * twitter: @xanxys_-* ブログの該当記事のコメント欄(初期バージョンのみ)+* ブログの該当記事のコメント欄(バージョン1未満のみ)+
Setup.hs view
@@ -1,3 +1,4 @@ import Distribution.Simple+ main=defaultMain 
hawitter.cabal view
@@ -1,24 +1,25 @@ name:            hawitter cabal-version:   >=1.2-version:         0.2+version:         0.3 author:          Daiki Handa <xanxys@gmail.com> maintainer:      Daiki Handa <xanxys@gmail.com>-synopsis:        A twitter client for GTK+. Alpha version.+synopsis:        A twitter client for GTK+. Beta version. license:         BSD3 license-file:    LICENSE category:        Web+homepage:        http://d.hatena.ne.jp/xanxys/20100318/1268917714 description:-  THIS SOFTWARE IS STILL IN ALPHA STATE.-  A slick twitter client for GTK. Features syntax highlighting of tweets, URL linking/shortening and-  'timeline modifier'.+  A slick twitter client for GTK. Features syntax highlighting of tweets, URL linking.+  Automatic URL shortening and 'timeline modifier' will be implemented in future versions.    build-type:      Simple-data-files:      hawitter.glade hawitter_128sq.svg README.en README.ja+data-files:      hawitter.glade hawitter.svg hawitter_busy.svg README.en README.ja tested-with:     GHC==6.10.4 executable hawitter     main-is: Main.hs     build-depends:-        base>=3, base<5, containers>=0.2, regex-compat>=0.7, time>=1.1.4, old-locale>=1,+        base>=3, base<5, containers>=0.2, mtl>=1, time>=1.1.4, old-locale>=1,         gtk>=0.10, glade>=0.10, gconf>=0.10,+        parsec>=3, regex-compat>=0.7,         network>=2.2, HTTP>=4000, json>=0.4, utf8-string>=0.3 
hawitter.glade view
@@ -4,7 +4,7 @@   <!-- interface-naming-policy project-wide -->   <widget class="GtkWindow" id="mainwindow">     <property name="title" translatable="yes">hawitter</property>-    <property name="icon">hawitter_128sq.svg</property>+    <property name="icon">hawitter.svg</property>     <child>       <widget class="GtkVBox" id="vbox1">         <property name="visible">True</property>@@ -106,7 +106,7 @@               </packing>             </child>             <child>-              <widget class="GtkEntry" id="entry1">+              <widget class="GtkEntry" id="modifierentry">                 <property name="visible">True</property>                 <property name="can_focus">True</property>                 <property name="invisible_char">&#x25CF;</property>@@ -140,6 +140,16 @@             <property name="position">2</property>           </packing>         </child>+        <child>+          <widget class="GtkStatusbar" id="statusbar">+            <property name="visible">True</property>+            <property name="spacing">2</property>+          </widget>+          <packing>+            <property name="expand">False</property>+            <property name="position">3</property>+          </packing>+        </child>       </widget>     </child>   </widget>@@ -148,6 +158,7 @@     <property name="title" translatable="yes">hawitter - account</property>     <property name="resizable">False</property>     <property name="modal">True</property>+    <property name="icon">hawitter.svg</property>     <property name="type_hint">normal</property>     <property name="has_separator">False</property>     <child internal-child="vbox">@@ -406,6 +417,7 @@     <property name="border_width">5</property>     <property name="title" translatable="yes">hawitter - tweet</property>     <property name="resizable">False</property>+    <property name="icon">hawitter.svg</property>     <property name="type_hint">normal</property>     <property name="has_separator">False</property>     <child internal-child="vbox">
+ hawitter.svg view
@@ -0,0 +1,110 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<svg+   xmlns:dc="http://purl.org/dc/elements/1.1/"+   xmlns:cc="http://creativecommons.org/ns#"+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"+   xmlns:svg="http://www.w3.org/2000/svg"+   xmlns="http://www.w3.org/2000/svg"+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"+   width="128"+   height="128"+   viewBox="0 0 102.4 102.39999"+   version="1.1"+   id="svg2900"+   inkscape:version="0.47 r22583"+   sodipodi:docname="hawitter_centered.svg"+   inkscape:export-filename="/home/xyx/devenv/hawitter/hawitter_128sq.png"+   inkscape:export-xdpi="16.002222"+   inkscape:export-ydpi="16.002222">+  <metadata+     id="metadata2920">+    <rdf:RDF>+      <cc:Work+         rdf:about="">+        <dc:format>image/svg+xml</dc:format>+        <dc:type+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />+        <dc:title></dc:title>+      </cc:Work>+    </rdf:RDF>+  </metadata>+  <sodipodi:namedview+     pagecolor="#ffffff"+     bordercolor="#666666"+     borderopacity="1"+     objecttolerance="10"+     gridtolerance="10"+     guidetolerance="10"+     inkscape:pageopacity="0"+     inkscape:pageshadow="2"+     inkscape:window-width="838"+     inkscape:window-height="1023"+     id="namedview2918"+     showgrid="false"+     inkscape:zoom="1.716227"+     inkscape:cx="-52.998198"+     inkscape:cy="78.331889"+     inkscape:window-x="0"+     inkscape:window-y="25"+     inkscape:window-maximized="0"+     inkscape:current-layer="svg2900" />+  <defs+     id="defs2902">+    <inkscape:perspective+       sodipodi:type="inkscape:persp3d"+       inkscape:vp_x="0 : 212.59839 : 1"+       inkscape:vp_y="0 : 1000 : 0"+       inkscape:vp_z="602.36212 : 212.59839 : 1"+       inkscape:persp3d-origin="301.18106 : 141.73226 : 1"+       id="perspective2922" />+    <clipPath+       id="clip1">+      <path+         d="m 0,340.15625 481.89062,0 L 481.89062,0 0,0 0,340.15625 z m 0,0"+         id="path2905" />+    </clipPath>+    <inkscape:perspective+       id="perspective3049"+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"+       inkscape:vp_z="1 : 0.5 : 1"+       inkscape:vp_y="0 : 1000 : 0"+       inkscape:vp_x="0 : 0.5 : 1"+       sodipodi:type="inkscape:persp3d" />+    <inkscape:perspective+       id="perspective3072"+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"+       inkscape:vp_z="1 : 0.5 : 1"+       inkscape:vp_y="0 : 1000 : 0"+       inkscape:vp_x="0 : 0.5 : 1"+       sodipodi:type="inkscape:persp3d" />+  </defs>+  <g+     id="g2818"+     transform="translate(6.4546249,0.48166647)">+    <path+       transform="matrix(0.17313019,0,0,0.183031,-3.6257093,19.552725)"+       d="M 0,-8.5625 A 8.5580266,8.5580266 0 0 0 -7.125,4.75 L 103.09375,170.0625 -7.125,335.40625 A 8.5580266,8.5580266 0 0 0 0,348.71875 l 85.03125,0 a 8.5580266,8.5580266 0 0 0 7.125,-3.8125 L 205.5625,174.8125 a 8.5580266,8.5580266 0 0 0 0,-9.5 L 92.15625,-4.75 a 8.5580266,8.5580266 0 0 0 -7.125,-3.8125 L 0,-8.5625 z"+       id="path3098"+       style="fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none"+       inkscape:original="M 0 0 L 113.375 170.0625 L 0 340.15625 L 85.03125 340.15625 L 198.4375 170.0625 L 85.03125 0 L 0 0 z "+       inkscape:radius="8.5571709"+       sodipodi:type="inkscape:offset" />+    <path+       transform="matrix(0.17313019,0,0,0.183031,-2.2406683,19.552725)"+       d="m 113.375,-8.3125 a 8.300195,8.300195 0 0 0 -6.90625,12.90625 l 110.34375,165.46875 -110.34375,165.5 a 8.300195,8.300195 0 0 0 6.90625,12.90625 l 85.0625,0 A 8.300195,8.300195 0 0 0 205.34375,344.75 L 269.28125,248.8125 333.25,344.75 a 8.300195,8.300195 0 0 0 6.90625,3.71875 l 85.03125,0 a 8.300195,8.300195 0 0 0 6.90625,-12.90625 L 205.34375,-4.59375 A 8.300195,8.300195 0 0 0 198.4375,-8.3125 l -85.0625,0 z"+       id="path3096"+       style="fill:#999999;fill-opacity:1;fill-rule:nonzero;stroke:none"+       inkscape:original="M 113.375 0 L 226.78125 170.0625 L 113.375 340.15625 L 198.4375 340.15625 L 269.28125 233.84375 L 340.15625 340.15625 L 425.1875 340.15625 L 198.4375 0 L 113.375 0 z "+       inkscape:radius="8.299365"+       sodipodi:type="inkscape:offset" />+    <path+       transform="matrix(0.13850415,0,0,0.1464248,-0.57861732,19.552725)"+       d="M 282.875,-11.3125 A 11.566895,11.566895 0 0 0 273.25,6.65625 l 283.46875,425.1875 a 11.566895,11.566895 0 0 0 9.625,5.15625 l 109.28125,0 a 11.566895,11.566895 0 0 0 11.5625,-11.5625 l 0,-70.875 A 11.566895,11.566895 0 0 0 675.625,343 l -44.0625,0 -123.65625,-185.4375 67.46875,0 A 11.566895,11.566895 0 0 0 586.9375,146 l 0,-70.875 A 11.566895,11.566895 0 0 0 575.375,63.5625 l -130.125,0 -46.46875,-69.71875 a 11.566895,11.566895 0 0 0 -9.625,-5.15625 l -106.28125,0 z"+       id="path3094"+       style="fill:#33ccff;fill-opacity:1;fill-rule:nonzero;stroke:none"+       inkscape:original="M 282.875 0.25 L 566.34375 425.4375 L 675.625 425.4375 L 675.625 354.5625 L 625.375 354.5625 L 486.3125 146 L 575.375 146 L 575.375 75.125 L 439.0625 75.125 L 389.15625 0.25 L 282.875 0.25 z "+       inkscape:radius="11.565739"+       sodipodi:type="inkscape:offset" />+  </g>+</svg>
− hawitter_128sq.svg
@@ -1,110 +0,0 @@-<?xml version="1.0" encoding="UTF-8" standalone="no"?>-<svg-   xmlns:dc="http://purl.org/dc/elements/1.1/"-   xmlns:cc="http://creativecommons.org/ns#"-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"-   xmlns:svg="http://www.w3.org/2000/svg"-   xmlns="http://www.w3.org/2000/svg"-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"-   width="128"-   height="128"-   viewBox="0 0 102.4 102.39999"-   version="1.1"-   id="svg2900"-   inkscape:version="0.47 r22583"-   sodipodi:docname="hawitter_centered.svg"-   inkscape:export-filename="/home/xyx/devenv/hawitter/hawitter_128sq.png"-   inkscape:export-xdpi="16.002222"-   inkscape:export-ydpi="16.002222">-  <metadata-     id="metadata2920">-    <rdf:RDF>-      <cc:Work-         rdf:about="">-        <dc:format>image/svg+xml</dc:format>-        <dc:type-           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />-        <dc:title></dc:title>-      </cc:Work>-    </rdf:RDF>-  </metadata>-  <sodipodi:namedview-     pagecolor="#ffffff"-     bordercolor="#666666"-     borderopacity="1"-     objecttolerance="10"-     gridtolerance="10"-     guidetolerance="10"-     inkscape:pageopacity="0"-     inkscape:pageshadow="2"-     inkscape:window-width="838"-     inkscape:window-height="1023"-     id="namedview2918"-     showgrid="false"-     inkscape:zoom="1.716227"-     inkscape:cx="-52.998198"-     inkscape:cy="78.331889"-     inkscape:window-x="0"-     inkscape:window-y="25"-     inkscape:window-maximized="0"-     inkscape:current-layer="svg2900" />-  <defs-     id="defs2902">-    <inkscape:perspective-       sodipodi:type="inkscape:persp3d"-       inkscape:vp_x="0 : 212.59839 : 1"-       inkscape:vp_y="0 : 1000 : 0"-       inkscape:vp_z="602.36212 : 212.59839 : 1"-       inkscape:persp3d-origin="301.18106 : 141.73226 : 1"-       id="perspective2922" />-    <clipPath-       id="clip1">-      <path-         d="m 0,340.15625 481.89062,0 L 481.89062,0 0,0 0,340.15625 z m 0,0"-         id="path2905" />-    </clipPath>-    <inkscape:perspective-       id="perspective3049"-       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"-       inkscape:vp_z="1 : 0.5 : 1"-       inkscape:vp_y="0 : 1000 : 0"-       inkscape:vp_x="0 : 0.5 : 1"-       sodipodi:type="inkscape:persp3d" />-    <inkscape:perspective-       id="perspective3072"-       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"-       inkscape:vp_z="1 : 0.5 : 1"-       inkscape:vp_y="0 : 1000 : 0"-       inkscape:vp_x="0 : 0.5 : 1"-       sodipodi:type="inkscape:persp3d" />-  </defs>-  <g-     id="g2818"-     transform="translate(6.4546249,0.48166647)">-    <path-       transform="matrix(0.17313019,0,0,0.183031,-3.6257093,19.552725)"-       d="M 0,-8.5625 A 8.5580266,8.5580266 0 0 0 -7.125,4.75 L 103.09375,170.0625 -7.125,335.40625 A 8.5580266,8.5580266 0 0 0 0,348.71875 l 85.03125,0 a 8.5580266,8.5580266 0 0 0 7.125,-3.8125 L 205.5625,174.8125 a 8.5580266,8.5580266 0 0 0 0,-9.5 L 92.15625,-4.75 a 8.5580266,8.5580266 0 0 0 -7.125,-3.8125 L 0,-8.5625 z"-       id="path3098"-       style="fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none"-       inkscape:original="M 0 0 L 113.375 170.0625 L 0 340.15625 L 85.03125 340.15625 L 198.4375 170.0625 L 85.03125 0 L 0 0 z "-       inkscape:radius="8.5571709"-       sodipodi:type="inkscape:offset" />-    <path-       transform="matrix(0.17313019,0,0,0.183031,-2.2406683,19.552725)"-       d="m 113.375,-8.3125 a 8.300195,8.300195 0 0 0 -6.90625,12.90625 l 110.34375,165.46875 -110.34375,165.5 a 8.300195,8.300195 0 0 0 6.90625,12.90625 l 85.0625,0 A 8.300195,8.300195 0 0 0 205.34375,344.75 L 269.28125,248.8125 333.25,344.75 a 8.300195,8.300195 0 0 0 6.90625,3.71875 l 85.03125,0 a 8.300195,8.300195 0 0 0 6.90625,-12.90625 L 205.34375,-4.59375 A 8.300195,8.300195 0 0 0 198.4375,-8.3125 l -85.0625,0 z"-       id="path3096"-       style="fill:#999999;fill-opacity:1;fill-rule:nonzero;stroke:none"-       inkscape:original="M 113.375 0 L 226.78125 170.0625 L 113.375 340.15625 L 198.4375 340.15625 L 269.28125 233.84375 L 340.15625 340.15625 L 425.1875 340.15625 L 198.4375 0 L 113.375 0 z "-       inkscape:radius="8.299365"-       sodipodi:type="inkscape:offset" />-    <path-       transform="matrix(0.13850415,0,0,0.1464248,-0.57861732,19.552725)"-       d="M 282.875,-11.3125 A 11.566895,11.566895 0 0 0 273.25,6.65625 l 283.46875,425.1875 a 11.566895,11.566895 0 0 0 9.625,5.15625 l 109.28125,0 a 11.566895,11.566895 0 0 0 11.5625,-11.5625 l 0,-70.875 A 11.566895,11.566895 0 0 0 675.625,343 l -44.0625,0 -123.65625,-185.4375 67.46875,0 A 11.566895,11.566895 0 0 0 586.9375,146 l 0,-70.875 A 11.566895,11.566895 0 0 0 575.375,63.5625 l -130.125,0 -46.46875,-69.71875 a 11.566895,11.566895 0 0 0 -9.625,-5.15625 l -106.28125,0 z"-       id="path3094"-       style="fill:#33ccff;fill-opacity:1;fill-rule:nonzero;stroke:none"-       inkscape:original="M 282.875 0.25 L 566.34375 425.4375 L 675.625 425.4375 L 675.625 354.5625 L 625.375 354.5625 L 486.3125 146 L 575.375 146 L 575.375 75.125 L 439.0625 75.125 L 389.15625 0.25 L 282.875 0.25 z "-       inkscape:radius="11.565739"-       sodipodi:type="inkscape:offset" />-  </g>-</svg>
+ hawitter_busy.svg view
@@ -0,0 +1,110 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<svg+   xmlns:dc="http://purl.org/dc/elements/1.1/"+   xmlns:cc="http://creativecommons.org/ns#"+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"+   xmlns:svg="http://www.w3.org/2000/svg"+   xmlns="http://www.w3.org/2000/svg"+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"+   width="128"+   height="128"+   viewBox="0 0 102.4 102.39999"+   version="1.1"+   id="svg2900"+   inkscape:version="0.47 r22583"+   sodipodi:docname="hawitter_busy.svg"+   inkscape:export-filename="/home/xyx/devenv/hawitter/hawitter_128sq.png"+   inkscape:export-xdpi="16.002222"+   inkscape:export-ydpi="16.002222">+  <metadata+     id="metadata2920">+    <rdf:RDF>+      <cc:Work+         rdf:about="">+        <dc:format>image/svg+xml</dc:format>+        <dc:type+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />+        <dc:title />+      </cc:Work>+    </rdf:RDF>+  </metadata>+  <sodipodi:namedview+     pagecolor="#ffffff"+     bordercolor="#666666"+     borderopacity="1"+     objecttolerance="10"+     gridtolerance="10"+     guidetolerance="10"+     inkscape:pageopacity="0"+     inkscape:pageshadow="2"+     inkscape:window-width="1413"+     inkscape:window-height="952"+     id="namedview2918"+     showgrid="false"+     inkscape:zoom="1.716227"+     inkscape:cx="-49.361244"+     inkscape:cy="30.421033"+     inkscape:window-x="0"+     inkscape:window-y="25"+     inkscape:window-maximized="0"+     inkscape:current-layer="svg2900" />+  <defs+     id="defs2902">+    <inkscape:perspective+       sodipodi:type="inkscape:persp3d"+       inkscape:vp_x="0 : 212.59839 : 1"+       inkscape:vp_y="0 : 1000 : 0"+       inkscape:vp_z="602.36212 : 212.59839 : 1"+       inkscape:persp3d-origin="301.18106 : 141.73226 : 1"+       id="perspective2922" />+    <clipPath+       id="clip1">+      <path+         d="m 0,340.15625 481.89062,0 L 481.89062,0 0,0 0,340.15625 z m 0,0"+         id="path2905" />+    </clipPath>+    <inkscape:perspective+       id="perspective3049"+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"+       inkscape:vp_z="1 : 0.5 : 1"+       inkscape:vp_y="0 : 1000 : 0"+       inkscape:vp_x="0 : 0.5 : 1"+       sodipodi:type="inkscape:persp3d" />+    <inkscape:perspective+       id="perspective3072"+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"+       inkscape:vp_z="1 : 0.5 : 1"+       inkscape:vp_y="0 : 1000 : 0"+       inkscape:vp_x="0 : 0.5 : 1"+       sodipodi:type="inkscape:persp3d" />+  </defs>+  <g+     id="g3597"+     transform="translate(6.4546249,0.48166647)">+    <path+       transform="matrix(0.17313019,0,0,0.183031,-3.6257093,19.552725)"+       d="M 0,-8.5625 A 8.5580266,8.5580266 0 0 0 -7.125,4.75 L 103.09375,170.0625 -7.125,335.40625 A 8.5580266,8.5580266 0 0 0 0,348.71875 l 85.03125,0 a 8.5580266,8.5580266 0 0 0 7.125,-3.8125 L 205.5625,174.8125 a 8.5580266,8.5580266 0 0 0 0,-9.5 L 92.15625,-4.75 a 8.5580266,8.5580266 0 0 0 -7.125,-3.8125 L 0,-8.5625 z"+       id="path3098"+       style="fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none"+       inkscape:original="M 0 0 L 113.375 170.0625 L 0 340.15625 L 85.03125 340.15625 L 198.4375 170.0625 L 85.03125 0 L 0 0 z "+       inkscape:radius="8.5571709"+       sodipodi:type="inkscape:offset" />+    <path+       transform="matrix(0.17313019,0,0,0.183031,-2.2406683,19.552725)"+       d="m 113.375,-8.3125 a 8.300195,8.300195 0 0 0 -6.90625,12.90625 l 110.34375,165.46875 -110.34375,165.5 a 8.300195,8.300195 0 0 0 6.90625,12.90625 l 85.0625,0 A 8.300195,8.300195 0 0 0 205.34375,344.75 L 269.28125,248.8125 333.25,344.75 a 8.300195,8.300195 0 0 0 6.90625,3.71875 l 85.03125,0 a 8.300195,8.300195 0 0 0 6.90625,-12.90625 L 205.34375,-4.59375 A 8.300195,8.300195 0 0 0 198.4375,-8.3125 l -85.0625,0 z"+       id="path3096"+       style="fill:#999999;fill-opacity:1;fill-rule:nonzero;stroke:none"+       inkscape:original="M 113.375 0 L 226.78125 170.0625 L 113.375 340.15625 L 198.4375 340.15625 L 269.28125 233.84375 L 340.15625 340.15625 L 425.1875 340.15625 L 198.4375 0 L 113.375 0 z "+       inkscape:radius="8.299365"+       sodipodi:type="inkscape:offset" />+    <path+       transform="matrix(0.13850415,0,0,0.1464248,-0.57861732,19.552725)"+       d="M 282.875,-11.3125 A 11.566895,11.566895 0 0 0 273.25,6.65625 l 283.46875,425.1875 a 11.566895,11.566895 0 0 0 9.625,5.15625 l 109.28125,0 a 11.566895,11.566895 0 0 0 11.5625,-11.5625 l 0,-70.875 A 11.566895,11.566895 0 0 0 675.625,343 l -44.0625,0 -123.65625,-185.4375 67.46875,0 A 11.566895,11.566895 0 0 0 586.9375,146 l 0,-70.875 A 11.566895,11.566895 0 0 0 575.375,63.5625 l -130.125,0 -46.46875,-69.71875 a 11.566895,11.566895 0 0 0 -9.625,-5.15625 l -106.28125,0 z"+       id="path3094"+       style="fill:#ff5333;fill-opacity:1;fill-rule:nonzero;stroke:none"+       inkscape:original="M 282.875 0.25 L 566.34375 425.4375 L 675.625 425.4375 L 675.625 354.5625 L 625.375 354.5625 L 486.3125 146 L 575.375 146 L 575.375 75.125 L 439.0625 75.125 L 389.15625 0.25 L 282.875 0.25 z "+       inkscape:radius="11.565739"+       sodipodi:type="inkscape:offset" />+  </g>+</svg>