From 4dc9d763baabff6e8d7284a5ef9781f65f28f2f0 Mon Sep 17 00:00:00 2001 From: Kolan Sh Date: Tue, 12 Mar 2013 00:32:44 +0400 Subject: [PATCH] Awesome-3.5 config added. --- Icons/16x16/battery.png | Bin 0 -> 191 bytes Icons/16x16/cpu.png | Bin 0 -> 204 bytes Icons/16x16/down.png | Bin 0 -> 298 bytes Icons/16x16/down_up.png | Bin 0 -> 218 bytes Icons/16x16/mem.png | Bin 0 -> 171 bytes Icons/16x16/net-wifi.png | Bin 0 -> 216 bytes Icons/16x16/net-wired.png | Bin 0 -> 217 bytes Icons/16x16/up.png | Bin 0 -> 299 bytes Icons/16x16/vol-hi.png | Bin 0 -> 247 bytes Icons/cpu.png | Bin 0 -> 1409 bytes Icons/mem.png | Bin 0 -> 5096 bytes Icons/volume.png | Bin 0 -> 1000 bytes Icons/weather.png | Bin 0 -> 4449 bytes aurup.lua | 33 + autostart.lua | 9 + awesompd/README.md | 19 + awesompd/asyncshell.lua | 67 + awesompd/awesompd.lua | 1140 +++++++++++++++++ awesompd/icons/check_icon.png | Bin 0 -> 241 bytes awesompd/icons/default_album_cover.png | Bin 0 -> 18940 bytes awesompd/icons/next_icon.png | Bin 0 -> 242 bytes awesompd/icons/pause_icon.png | Bin 0 -> 210 bytes awesompd/icons/play_icon.png | Bin 0 -> 234 bytes awesompd/icons/play_pause_icon.png | Bin 0 -> 248 bytes awesompd/icons/prev_icon.png | Bin 0 -> 240 bytes awesompd/icons/radio_icon.png | Bin 0 -> 342 bytes awesompd/icons/stop_icon.png | Bin 0 -> 210 bytes awesompd/jamendo.lua | 524 ++++++++ awesompd/rcsample.lua | 407 ++++++ awesompd/utf8.lua | 158 +++ bitcoin.lua | 50 + diskusage.lua | 133 ++ freedesktop/desktop.lua | 126 ++ freedesktop/menu.lua | 97 ++ freedesktop/utils.lua | 255 ++++ gfxtemp.lua | 33 + gmail_parser.py | 47 + html.lua | 95 ++ keybinds.lua | 119 ++ keydoc.lua | 123 ++ list.lua | 36 + namecoin.lua | 33 + pacmanup.lua | 34 + perceptive/.gitignore | 1 + perceptive/README.md | 43 + perceptive/init.lua | 83 ++ perceptive/screenshot.png | Bin 0 -> 6353 bytes perceptive/weather-fetcher.py | 87 ++ rc.lua | 601 +++++++++ set_wall.sh | 3 + sysinf.lua | 33 + themes/default/README | 3 + themes/default/icon/Arch.png | Bin 0 -> 1730 bytes themes/default/icon/Arch.png.bak | Bin 0 -> 1730 bytes themes/default/icon/awesome16.png | Bin 0 -> 1987 bytes themes/default/layouts/dwindle.png | Bin 0 -> 967 bytes themes/default/layouts/dwindlew.png | Bin 0 -> 997 bytes themes/default/layouts/fairh.png | Bin 0 -> 194 bytes themes/default/layouts/fairhw.png | Bin 0 -> 194 bytes themes/default/layouts/fairv.png | Bin 0 -> 201 bytes themes/default/layouts/fairvw.png | Bin 0 -> 201 bytes themes/default/layouts/floating.png | Bin 0 -> 395 bytes themes/default/layouts/floatingw.png | Bin 0 -> 388 bytes themes/default/layouts/fullscreen.png | Bin 0 -> 202 bytes themes/default/layouts/fullscreenw.png | Bin 0 -> 202 bytes themes/default/layouts/magnifier.png | Bin 0 -> 209 bytes themes/default/layouts/magnifierw.png | Bin 0 -> 209 bytes themes/default/layouts/max.png | Bin 0 -> 321 bytes themes/default/layouts/maxw.png | Bin 0 -> 321 bytes themes/default/layouts/spiral.png | Bin 0 -> 1506 bytes themes/default/layouts/spiralw.png | Bin 0 -> 1527 bytes themes/default/layouts/tile.png | Bin 0 -> 174 bytes themes/default/layouts/tilebottom.png | Bin 0 -> 195 bytes themes/default/layouts/tilebottomw.png | Bin 0 -> 216 bytes themes/default/layouts/tileleft.png | Bin 0 -> 172 bytes themes/default/layouts/tileleftw.png | Bin 0 -> 170 bytes themes/default/layouts/tiletop.png | Bin 0 -> 195 bytes themes/default/layouts/tiletopw.png | Bin 0 -> 215 bytes themes/default/layouts/tilew.png | Bin 0 -> 168 bytes themes/default/submenu.png | Bin 0 -> 440 bytes themes/default/taglist/squarefw.png | Bin 0 -> 198 bytes themes/default/taglist/squarefw.png2 | Bin 0 -> 187 bytes themes/default/taglist/squarew.png | Bin 0 -> 278 bytes themes/default/taglist/squarew.png2 | Bin 0 -> 193 bytes themes/default/tasklist/floating.png | Bin 0 -> 345 bytes themes/default/tasklist/floatingw.png | Bin 0 -> 334 bytes themes/default/theme.lua | 149 +++ themes/default/titlebar/close_focus.png | Bin 0 -> 666 bytes themes/default/titlebar/close_normal.png | Bin 0 -> 905 bytes .../titlebar/floating_focus_active.png | Bin 0 -> 598 bytes .../titlebar/floating_focus_inactive.png | Bin 0 -> 623 bytes .../titlebar/floating_normal_active.png | Bin 0 -> 876 bytes .../titlebar/floating_normal_inactive.png | Bin 0 -> 614 bytes .../titlebar/maximized_focus_active.png | Bin 0 -> 1013 bytes .../titlebar/maximized_focus_inactive.png | Bin 0 -> 829 bytes .../titlebar/maximized_normal_active.png | Bin 0 -> 1555 bytes .../titlebar/maximized_normal_inactive.png | Bin 0 -> 816 bytes .../default/titlebar/ontop_focus_active.png | Bin 0 -> 774 bytes .../default/titlebar/ontop_focus_inactive.png | Bin 0 -> 758 bytes .../default/titlebar/ontop_normal_active.png | Bin 0 -> 1295 bytes .../titlebar/ontop_normal_inactive.png | Bin 0 -> 756 bytes .../default/titlebar/sticky_focus_active.png | Bin 0 -> 833 bytes .../titlebar/sticky_focus_inactive.png | Bin 0 -> 663 bytes .../default/titlebar/sticky_normal_active.png | Bin 0 -> 1465 bytes .../titlebar/sticky_normal_inactive.png | Bin 0 -> 677 bytes themes/default/widgets/ac.png | Bin 0 -> 986 bytes themes/default/widgets/acblink.png | Bin 0 -> 820 bytes themes/default/widgets/bat.png | Bin 0 -> 740 bytes themes/default/widgets/batempty.png | Bin 0 -> 866 bytes themes/default/widgets/batfull.png | Bin 0 -> 932 bytes themes/default/widgets/batlow.png | Bin 0 -> 917 bytes themes/default/widgets/batmed.png | Bin 0 -> 932 bytes themes/default/widgets/blank.png | Bin 0 -> 305 bytes themes/default/widgets/cpu.png | Bin 0 -> 601 bytes themes/default/widgets/crit.png | Bin 0 -> 727 bytes themes/default/widgets/disk.png | Bin 0 -> 897 bytes themes/default/widgets/down.png | Bin 0 -> 938 bytes themes/default/widgets/fs.png | Bin 0 -> 783 bytes themes/default/widgets/fs2.png | Bin 0 -> 803 bytes themes/default/widgets/mail.png | Bin 0 -> 669 bytes themes/default/widgets/mailnew.png | Bin 0 -> 722 bytes themes/default/widgets/mpd.png | Bin 0 -> 956 bytes themes/default/widgets/mpdplay.png | Bin 0 -> 2454 bytes themes/default/widgets/mute.png | Bin 0 -> 1013 bytes themes/default/widgets/note.png | Bin 0 -> 731 bytes themes/default/widgets/note2.png | Bin 0 -> 936 bytes themes/default/widgets/note3.png | Bin 0 -> 888 bytes themes/default/widgets/nowifi.png | Bin 0 -> 737 bytes themes/default/widgets/pac.png | Bin 0 -> 899 bytes themes/default/widgets/pacnew.png | Bin 0 -> 902 bytes themes/default/widgets/pause.png | Bin 0 -> 2454 bytes themes/default/widgets/play.png | Bin 0 -> 2454 bytes themes/default/widgets/ram.png | Bin 0 -> 691 bytes themes/default/widgets/temp.png | Bin 0 -> 1079 bytes themes/default/widgets/temphot.png | Bin 0 -> 1075 bytes themes/default/widgets/tempwarm.png | Bin 0 -> 1079 bytes themes/default/widgets/up.png | Bin 0 -> 929 bytes themes/default/widgets/vol.png | Bin 0 -> 959 bytes themes/default/widgets/wifi.png | Bin 0 -> 720 bytes ticker.py | 36 + wallpaper.lua | 2 + weather.png | Bin 0 -> 1055 bytes wi.lua | 222 ++++ widgets/BTC.lua | 54 + widgets/pacman.lua | 11 + 145 files changed, 4866 insertions(+) create mode 100644 Icons/16x16/battery.png create mode 100644 Icons/16x16/cpu.png create mode 100644 Icons/16x16/down.png create mode 100644 Icons/16x16/down_up.png create mode 100644 Icons/16x16/mem.png create mode 100644 Icons/16x16/net-wifi.png create mode 100644 Icons/16x16/net-wired.png create mode 100644 Icons/16x16/up.png create mode 100644 Icons/16x16/vol-hi.png create mode 100644 Icons/cpu.png create mode 100644 Icons/mem.png create mode 100644 Icons/volume.png create mode 100644 Icons/weather.png create mode 100644 aurup.lua create mode 100644 autostart.lua create mode 100644 awesompd/README.md create mode 100644 awesompd/asyncshell.lua create mode 100644 awesompd/awesompd.lua create mode 100644 awesompd/icons/check_icon.png create mode 100644 awesompd/icons/default_album_cover.png create mode 100644 awesompd/icons/next_icon.png create mode 100644 awesompd/icons/pause_icon.png create mode 100644 awesompd/icons/play_icon.png create mode 100644 awesompd/icons/play_pause_icon.png create mode 100644 awesompd/icons/prev_icon.png create mode 100644 awesompd/icons/radio_icon.png create mode 100644 awesompd/icons/stop_icon.png create mode 100644 awesompd/jamendo.lua create mode 100644 awesompd/rcsample.lua create mode 100644 awesompd/utf8.lua create mode 100644 bitcoin.lua create mode 100644 diskusage.lua create mode 100644 freedesktop/desktop.lua create mode 100644 freedesktop/menu.lua create mode 100644 freedesktop/utils.lua create mode 100644 gfxtemp.lua create mode 100644 gmail_parser.py create mode 100644 html.lua create mode 100644 keybinds.lua create mode 100644 keydoc.lua create mode 100644 list.lua create mode 100644 namecoin.lua create mode 100644 pacmanup.lua create mode 100644 perceptive/.gitignore create mode 100644 perceptive/README.md create mode 100644 perceptive/init.lua create mode 100644 perceptive/screenshot.png create mode 100755 perceptive/weather-fetcher.py create mode 100644 rc.lua create mode 100755 set_wall.sh create mode 100644 sysinf.lua create mode 100644 themes/default/README create mode 100644 themes/default/icon/Arch.png create mode 100644 themes/default/icon/Arch.png.bak create mode 100644 themes/default/icon/awesome16.png create mode 100644 themes/default/layouts/dwindle.png create mode 100644 themes/default/layouts/dwindlew.png create mode 100644 themes/default/layouts/fairh.png create mode 100644 themes/default/layouts/fairhw.png create mode 100644 themes/default/layouts/fairv.png create mode 100644 themes/default/layouts/fairvw.png create mode 100644 themes/default/layouts/floating.png create mode 100644 themes/default/layouts/floatingw.png create mode 100644 themes/default/layouts/fullscreen.png create mode 100644 themes/default/layouts/fullscreenw.png create mode 100644 themes/default/layouts/magnifier.png create mode 100644 themes/default/layouts/magnifierw.png create mode 100644 themes/default/layouts/max.png create mode 100644 themes/default/layouts/maxw.png create mode 100644 themes/default/layouts/spiral.png create mode 100644 themes/default/layouts/spiralw.png create mode 100644 themes/default/layouts/tile.png create mode 100644 themes/default/layouts/tilebottom.png create mode 100644 themes/default/layouts/tilebottomw.png create mode 100644 themes/default/layouts/tileleft.png create mode 100644 themes/default/layouts/tileleftw.png create mode 100644 themes/default/layouts/tiletop.png create mode 100644 themes/default/layouts/tiletopw.png create mode 100644 themes/default/layouts/tilew.png create mode 100644 themes/default/submenu.png create mode 100644 themes/default/taglist/squarefw.png create mode 100644 themes/default/taglist/squarefw.png2 create mode 100644 themes/default/taglist/squarew.png create mode 100644 themes/default/taglist/squarew.png2 create mode 100644 themes/default/tasklist/floating.png create mode 100644 themes/default/tasklist/floatingw.png create mode 100644 themes/default/theme.lua create mode 100644 themes/default/titlebar/close_focus.png create mode 100644 themes/default/titlebar/close_normal.png create mode 100644 themes/default/titlebar/floating_focus_active.png create mode 100644 themes/default/titlebar/floating_focus_inactive.png create mode 100644 themes/default/titlebar/floating_normal_active.png create mode 100644 themes/default/titlebar/floating_normal_inactive.png create mode 100644 themes/default/titlebar/maximized_focus_active.png create mode 100644 themes/default/titlebar/maximized_focus_inactive.png create mode 100644 themes/default/titlebar/maximized_normal_active.png create mode 100644 themes/default/titlebar/maximized_normal_inactive.png create mode 100644 themes/default/titlebar/ontop_focus_active.png create mode 100644 themes/default/titlebar/ontop_focus_inactive.png create mode 100644 themes/default/titlebar/ontop_normal_active.png create mode 100644 themes/default/titlebar/ontop_normal_inactive.png create mode 100644 themes/default/titlebar/sticky_focus_active.png create mode 100644 themes/default/titlebar/sticky_focus_inactive.png create mode 100644 themes/default/titlebar/sticky_normal_active.png create mode 100644 themes/default/titlebar/sticky_normal_inactive.png create mode 100644 themes/default/widgets/ac.png create mode 100644 themes/default/widgets/acblink.png create mode 100644 themes/default/widgets/bat.png create mode 100644 themes/default/widgets/batempty.png create mode 100644 themes/default/widgets/batfull.png create mode 100644 themes/default/widgets/batlow.png create mode 100644 themes/default/widgets/batmed.png create mode 100644 themes/default/widgets/blank.png create mode 100644 themes/default/widgets/cpu.png create mode 100644 themes/default/widgets/crit.png create mode 100644 themes/default/widgets/disk.png create mode 100644 themes/default/widgets/down.png create mode 100644 themes/default/widgets/fs.png create mode 100644 themes/default/widgets/fs2.png create mode 100644 themes/default/widgets/mail.png create mode 100644 themes/default/widgets/mailnew.png create mode 100644 themes/default/widgets/mpd.png create mode 100644 themes/default/widgets/mpdplay.png create mode 100644 themes/default/widgets/mute.png create mode 100644 themes/default/widgets/note.png create mode 100644 themes/default/widgets/note2.png create mode 100644 themes/default/widgets/note3.png create mode 100644 themes/default/widgets/nowifi.png create mode 100644 themes/default/widgets/pac.png create mode 100644 themes/default/widgets/pacnew.png create mode 100644 themes/default/widgets/pause.png create mode 100644 themes/default/widgets/play.png create mode 100644 themes/default/widgets/ram.png create mode 100644 themes/default/widgets/temp.png create mode 100644 themes/default/widgets/temphot.png create mode 100644 themes/default/widgets/tempwarm.png create mode 100644 themes/default/widgets/up.png create mode 100644 themes/default/widgets/vol.png create mode 100644 themes/default/widgets/wifi.png create mode 100644 ticker.py create mode 100644 wallpaper.lua create mode 100644 weather.png create mode 100644 wi.lua create mode 100644 widgets/BTC.lua create mode 100644 widgets/pacman.lua diff --git a/Icons/16x16/battery.png b/Icons/16x16/battery.png new file mode 100644 index 0000000000000000000000000000000000000000..ea260fa0db14578376bda711138e15759d5f9cba GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP)ql^%z?2EleJb*$zo-U3d7N?WvY)NEfR&D5PbaIGh zPI~j_-|-ti{~ll7#yZ_C=Z|W;nW0cBgW1s?F-9K}4NfecC~=_4UnqKuU@pVL1BbI2 f49)oGIj}IyJ}$Z9NnUv&&|C&jS3j3^P6G7;mFgFm?DWpgOy7j s*y}lOIBEFsvv!rqBoS_94SfcNz5C7M*|&Z?4YY*8)78&qol`;+0319#-v9sr literal 0 HcmV?d00001 diff --git a/Icons/16x16/down.png b/Icons/16x16/down.png new file mode 100644 index 0000000000000000000000000000000000000000..724293f393a0cdde2637ece341b50ca87fc27f33 GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx?BpA#)4xIr~Ea{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=cu> z$S;_Ip=|P53lJ~K+uenM@oty!5+IMWz$3C4NPB>>+sSM@AVbvC#W95AdUC=6wj2Bg z2Id9^NfQ|vzm0Xkxq!^4042^UR%ykVdLJZ8U42`Tz vOtcM5tPBj+p8YF~q9HdwB{QuOU4wy@u_;7D$@S^>Kn)C@u6{1-oD!M4nJ za0`PlBg3pY5H=O_PdNSd<^E_f4n&i6w3B=aSYKozjk6Q*P#R+mh|rJLYe$} zr_`?{m)2;0yDsMSSwxL{w(o-@o1zvcznGVB{n6*wr9*<`#o6j1ZopIJ#w)&y?A{XsF6T|ks09wo7 M>FVdQ&MBb@0F3@n>i_@% literal 0 HcmV?d00001 diff --git a/Icons/16x16/mem.png b/Icons/16x16/mem.png new file mode 100644 index 0000000000000000000000000000000000000000..5177a0d51f56b60bbaaa958900b5edfdd2c33c1a GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP)qYST%Qk}?a9iWhfr;B5V#p&cf|Nq-Ft2T5tIvIHI z?kInMufE2_C}D|-K;(y=4AKEXK}j-c8xMF)p5k#mLxyc51B0`L&g~cTyySppFnGH9 KxvX&#PhGPeeW?Ft<^%H0zgQu&X%Q~loCIC#4 BM}YtU literal 0 HcmV?d00001 diff --git a/Icons/16x16/net-wired.png b/Icons/16x16/net-wired.png new file mode 100644 index 0000000000000000000000000000000000000000..3e678d279411f5ace723e5b927f3aa2bb8f5fe4f GIT binary patch literal 217 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6n3BBRT^Rni_n+Ah4nJ za0`PlBg3pY5H=O_PdNSyvDM(^8VETg|a+d978nDU%jxK_fUX<%f-(xP8zxf zFvw1d^iWyi!D6vg^u3)fqf^8Tri4S*i~sWcU%o4Fy6VlE8?u4V*Eh~8=@c>Dm(jfI zwUbw-n3H%~;(amUbxvDyWm_|LU+pqG@tilm`tQ+Bt3OfO`Tt4jm54VVvH{x4;OXk; Jvd$@?2>_?=N;d!i literal 0 HcmV?d00001 diff --git a/Icons/16x16/up.png b/Icons/16x16/up.png new file mode 100644 index 0000000000000000000000000000000000000000..74a3db6988d99f9e2eb23645c1fd205d6e9ff97f GIT binary patch literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx?BpA#)4xIr~Ea{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=cu> z$S;_Ip=|P53lJ~K+uenM@oty!5+IMWz$3C4NPB>>+sSM@AVbX4#W95AdUC=6HVH|I z8PXCr_!CYsFi1r+`ux}$Jq4&jwZt`|BqgyV)hf9t6-Y4{85kPr8kp-ET7(#wTNxTz xnV4uBm{=JYtUdcz7)3*FeoAIqCAtO!D`QiLhLY>k?SUE?JYD@<);T3K0RVV_N+kdQ literal 0 HcmV?d00001 diff --git a/Icons/16x16/vol-hi.png b/Icons/16x16/vol-hi.png new file mode 100644 index 0000000000000000000000000000000000000000..7fbd2c979a9d843802e7093638c12e4f22af66b9 GIT binary patch literal 247 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6n3BBRT^Rni_n+Ah4nJ za0`PlBg3pY5H=O_PdNSycUeQv#Wmqg*rT4978nDuf1T%#pKA-`Y`|KFxjVH#U1nzO`1b1lYeqLyx3ZhM2b_U!V(@hJb6Mw<&;$TA?O;9t literal 0 HcmV?d00001 diff --git a/Icons/cpu.png b/Icons/cpu.png new file mode 100644 index 0000000000000000000000000000000000000000..7b2a3fb155a3acaf3b81a8e7fa1e359453da9183 GIT binary patch literal 1409 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 B*Es+H literal 0 HcmV?d00001 diff --git a/Icons/volume.png b/Icons/volume.png new file mode 100644 index 0000000000000000000000000000000000000000..1ff70d18bf66075ebcb1b7012418e04a07015a2d GIT binary patch literal 1000 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 literal 0 HcmV?d00001 diff --git a/aurup.lua b/aurup.lua new file mode 100644 index 0000000..771d018 --- /dev/null +++ b/aurup.lua @@ -0,0 +1,33 @@ +-- {{{ init environment +local wakka = {} +local capi = { + mouse = mouse, + screen = screen +} + +-- {{{ display +-- formats the lines for the notify +local function display() + local lines = "AUR Updates:\n" + local f = io.popen("cower -u", "r") + local s = f:read('*all') + line = lines .. "\n" .. s .. "\n" + f:close() + return line +end +-- }}} +-- }}} + +function wakka.addToWidget(mywidget) + mywidget:add_signal('mouse::enter', function () + usage = naughty.notify({ + text = string.format('%s', "monospace", display()), + timeout = 0, + hover_timeout = 0.5, + screen = capi.mouse.screen + }) + end) + mywidget:add_signal('mouse::leave', function () naughty.destroy(usage) end) +end + +return wakka \ No newline at end of file diff --git a/autostart.lua b/autostart.lua new file mode 100644 index 0000000..0160fa3 --- /dev/null +++ b/autostart.lua @@ -0,0 +1,9 @@ +os.execute ("numlockx on &") +os.execute ("xsetroot -cursor_name left_ptr &") +os.execute ("xscreensaver -no-splash &") +os.execute ("pidgin &") +os.execute ("pgrep stardict || stardict &") +os.execute ("pgrep urxvt || urxvt &") +os.execute ("pgrep firefox || firefox &") +os.execute ("nvidia-settings -a GPUOverclockingState=1 -a GPU2DClockFreqs=135,135 -a GPU3DClockFreqs=629,480") +os.execute (os.getenv("HOME").."/.config/awesome/set_wall.sh "..os.getenv("HOME").."/wallpapers/") diff --git a/awesompd/README.md b/awesompd/README.md new file mode 100644 index 0000000..739800b --- /dev/null +++ b/awesompd/README.md @@ -0,0 +1,19 @@ +## Description ## + +This is an advanced MPD widget\client for AwesomeWM. + +For the detailed installation guide please see http://awesome.naquadah.org/wiki/Awesompd_widget . + +Also you can find an example of the widget configuration in the file rcsample.lua. + +## Version explanation ## + +Use this version with Awesome v3.4.x. If you are using the git pre-4.0 version of Awesome, please consider using [this](https://github.com/alexander-yakushev/awesompd/tree/for-awesome-git) version instead. + +### Changes in 1.1.0 ### + +* Album covers are now also shown for the local tracks (images are taken from the current track's folder) +* When the Jamendo track is playing you can visit artist's or album's page from the Jamendo menu +* Notification now shows the album name for the current track (for both local and Jamendo tracks) +* A few minor modifications and bugfixes + diff --git a/awesompd/asyncshell.lua b/awesompd/asyncshell.lua new file mode 100644 index 0000000..7510065 --- /dev/null +++ b/awesompd/asyncshell.lua @@ -0,0 +1,67 @@ +-- Asynchronous io.popen for Awesome WM. +-- How to use... +-- ...asynchronously: +-- asyncshell.request('wscript -Kiev', function(f) wwidget.text = f:read("*l") end) +-- ...synchronously +-- wwidget.text = asyncshell.demand('wscript -Kiev', 5):read("*l") or "Error" + +asyncshell = {} +asyncshell.request_table = {} +asyncshell.id_counter = 0 +asyncshell.folder = "/tmp/asyncshell" +asyncshell.file_template = asyncshell.folder .. '/req' + +-- Create a directory for asynchell response files +os.execute("mkdir -p " .. asyncshell.folder) + +-- Returns next tag - unique identifier of the request +local function next_id() + asyncshell.id_counter = (asyncshell.id_counter + 1) % 100000 + return asyncshell.id_counter +end + +-- Sends an asynchronous request for an output of the shell command. +-- @param command Command to be executed and taken output from +-- @param callback Function to be called when the command finishes +-- @return Request ID +function asyncshell.request(command, callback) + local id = next_id() + local tmpfname = asyncshell.file_template .. id + asyncshell.request_table[id] = {callback = callback} + local req = + string.format("bash -c '%s > %s; " .. + 'echo "asyncshell.deliver(%s)" | ' .. + "awesome-client' 2> /dev/null", + string.gsub(command, "'", "'\\''"), tmpfname, id, tmpfname) + awful.util.spawn(req) + return id +end + +-- Calls the remembered callback function on the output of the shell +-- command. +-- @param id Request ID +-- @param output The output file of the shell command to be delievered +function asyncshell.deliver(id) + if asyncshell.request_table[id] and + asyncshell.request_table[id].callback then + local output = io.open(asyncshell.file_template .. id, 'r') + asyncshell.request_table[id].callback(output) + end +end + +-- Sends a synchronous request for an output of the command. Waits for +-- the output, but if the given timeout expires returns nil. +-- @param command Command to be executed and taken output from +-- @param timeout Maximum amount of time to wait for the result +-- @return File handler on success, nil otherwise +function asyncshell.demand(command, timeout) + local id = next_id() + local tmpfname = asyncshell.file_template .. id + local f = io.popen(string.format("(%s > %s; echo asyncshell_done) & " .. + "(sleep %s; echo asynchell_timeout)", + command, tmpfname, timeout)) + local result = f:read("*line") + if result == "asyncshell_done" then + return io.open(tmpfname) + end +end \ No newline at end of file diff --git a/awesompd/awesompd.lua b/awesompd/awesompd.lua new file mode 100644 index 0000000..faf1253 --- /dev/null +++ b/awesompd/awesompd.lua @@ -0,0 +1,1140 @@ +--------------------------------------------------------------------------- +-- @author Alexander Yakushev +-- @copyright 2010-2011 Alexander Yakushev +-- @release v1.1.5 +--------------------------------------------------------------------------- + +awesompd = {} + +-- Function for checking icons and modules. Checks if a file exists, +-- and if it does, returns the path to file, nil otherwise. +function awesompd.try_load(file) + if awful.util.file_readable(file) then + return file + end +end + +-- Function for loading modules. +function awesompd.try_require(module) + if awesompd.try_load(awful.util.getdir("config") .. + "/awesompd/" .. module .. ".lua") then + return require('awesompd/' .. module) + else + return require(module) + end +end + +awesompd.try_require("utf8") +awesompd.try_require("asyncshell") +awesompd.try_require("jamendo") +local beautiful = require('beautiful') +local naughty = naughty +local awful = awful +local format = string.format +local keygrabber = keygrabber + +-- Debug stuff + +local enable_dbg = true +local function dbg (...) + if enable_dbg then + print(...) + end +end + +local function tbl_pr(tbl,shift) + if enable_dbg then + local shift = shift or "" + for k, v in pairs(tbl) do + print(shift .. k .. ": " .. tostring(v)) + if type(v) == "table" then + tbl_pr(v, shift .. " ") + end + end + end +end + +-- Constants +awesompd.PLAYING = "Playing" +awesompd.PAUSED = "Paused" +awesompd.STOPPED = "MPD stopped" +awesompd.DISCONNECTED = "Disconnected" + +awesompd.MOUSE_LEFT = 1 +awesompd.MOUSE_MIDDLE = 2 +awesompd.MOUSE_RIGHT = 3 +awesompd.MOUSE_SCROLL_UP = 4 +awesompd.MOUSE_SCROLL_DOWN = 5 + +awesompd.NOTIFY_VOLUME = 1 +awesompd.NOTIFY_REPEAT = 2 +awesompd.NOTIFY_RANDOM = 3 +awesompd.NOTIFY_SINGLE = 4 +awesompd.NOTIFY_CONSUME = 5 +awesompd.FORMAT_MP3 = jamendo.FORMAT_MP3 +awesompd.FORMAT_OGG = jamendo.FORMAT_OGG +awesompd.ESCAPE_SYMBOL_MAPPING = {} +awesompd.ESCAPE_SYMBOL_MAPPING["&"] = "&" +-- Menus do not handle symbol escaping correctly, so they need their +-- own mapping. +awesompd.ESCAPE_MENU_SYMBOL_MAPPING = {} +awesompd.ESCAPE_MENU_SYMBOL_MAPPING["&"] = "'n'" + +-- /// Current track variables and functions /// + +-- Returns a string for the given track to be displayed in the widget +-- and notification. +function awesompd.get_display_name(track) + if track.display_name then + return track.display_name + elseif track.artist_name and track.track_name then + return track.artist_name .. " - " .. track.name + end +end + +-- Returns a track display name, album name (if exists) and album +-- release year (if exists). +function awesompd.get_extended_info(track) + local result = awesompd.get_display_name(track) + if track.album_name then + result = result .. "\n" .. track.album_name + end + if track.year then + result = result .. "\n" .. track.year + end + return result +end + +-- Returns true if the current status is either PLAYING or PAUSED +function awesompd:playing_or_paused() + return self.status == awesompd.PLAYING + or self.status == awesompd.PAUSED +end + +-- /// Helper functions /// + +-- Just like awful.util.pread, but takes an argument how to read like +-- "*line" or "*all". +function awesompd.pread(com, mode) + local f = io.popen(com, 'r') + local result = nil + if f then + result = f:read(mode) + f:close() + end + return result +end + +-- Slightly modified function awful.util.table.join. +function awesompd.ajoin(buttons) + local result = {} + for i = 1, table.getn(buttons) do + if buttons[i] then + for k, v in pairs(buttons[i]) do + if type(k) == "number" then + table.insert(result, v) + else + result[k] = v + end + end + end + end + return result + end + +-- Splits a given string with linebreaks into an array. +function awesompd.split(s) + local l = { n = 0 } + if s == "" then + return l + end + s = s .. "\n" + local f = function (s) + l.n = l.n + 1 + l[l.n] = s + end + local p = "%s*(.-)%s*\n%s*" + s = string.gsub(s,p,f) + return l +end + +-- Icons + +function awesompd.load_icons(path) + awesompd.ICONS = {} + awesompd.ICONS.PLAY = awesompd.try_load(path .. "/play_icon.png") + awesompd.ICONS.PAUSE = awesompd.try_load(path .. "/pause_icon.png") + awesompd.ICONS.PLAY_PAUSE = awesompd.try_load(path .. "/play_pause_icon.png") + awesompd.ICONS.STOP = awesompd.try_load(path .. "/stop_icon.png") + awesompd.ICONS.NEXT = awesompd.try_load(path .. "/next_icon.png") + awesompd.ICONS.PREV = awesompd.try_load(path .. "/prev_icon.png") + awesompd.ICONS.CHECK = awesompd.try_load(path .. "/check_icon.png") + awesompd.ICONS.RADIO = awesompd.try_load(path .. "/radio_icon.png") + awesompd.ICONS.DEFAULT_ALBUM_COVER = + awesompd.try_load(path .. "/default_album_cover.png") +end + +-- Function that returns a new awesompd object. +function awesompd:create() +-- Initialization + local instance = {} + setmetatable(instance,self) + self.__index = self + instance.current_server = 1 + instance.widget = widget({ type = "textbox" }) + instance.notification = nil + instance.scroll_pos = 1 + instance.text = "" + instance.to_notify = false + instance.album_cover = nil + instance.current_track = { } + instance.recreate_menu = true + instance.recreate_playback = true + instance.recreate_list = true + instance.recreate_servers = true + instance.recreate_options = true + instance.recreate_jamendo_formats = true + instance.recreate_jamendo_order = true + instance.recreate_jamendo_browse = true + instance.current_number = 0 + instance.menu_shown = false + +-- Default user options + instance.servers = { { server = "localhost", port = 6600 } } + instance.font = "Monospace" + instance.scrolling = true + instance.output_size = 30 + instance.update_interval = 10 + instance.path_to_icons = "" + instance.ldecorator = " " + instance.rdecorator = " " + instance.jamendo_format = awesompd.FORMAT_MP3 + instance.show_album_cover = true + instance.album_cover_size = 50 + instance.browser = "firefox" + +-- Widget configuration + instance.widget:add_signal("mouse::enter", function(c) + instance:notify_track() + end) + instance.widget:add_signal("mouse::leave", function(c) + instance:remove_hint() + end) + return instance +end + +-- Registers timers for the widget +function awesompd:run() + enable_dbg = self.debug_mode + self.load_icons(self.path_to_icons) + jamendo.set_current_format(self.jamendo_format) + if self.album_cover_size > 100 then + self.album_cover_size = 100 + end + + self:update_track() + self:check_playlists() + self.update_widget_timer = timer({ timeout = 1 }) + self.update_widget_timer:add_signal("timeout", function() + self:update_widget() + end) + self.update_widget_timer:start() + self.update_track_timer = timer({ timeout = self.update_interval }) + self.update_track_timer:add_signal("timeout", function() + self:update_track() + end) + self.update_track_timer:start() +end + +-- Function that registers buttons on the widget. +function awesompd:register_buttons(buttons) + widget_buttons = {} + self.global_bindings = {} + for b=1,table.getn(buttons) do + if type(buttons[b][1]) == "string" then + mods = { buttons[b][1] } + else + mods = buttons[b][1] + end + if type(buttons[b][2]) == "number" then + -- This is a mousebinding, bind it to the widget + table.insert(widget_buttons, + awful.button(mods, buttons[b][2], buttons[b][3])) + else + -- This is a global keybinding, remember it for later usage in append_global_keys + table.insert(self.global_bindings, awful.key(mods, buttons[b][2], buttons[b][3])) + end + end + self.widget:buttons(self.ajoin(widget_buttons)) +end + +-- Takes the current table with keybindings and adds widget's own +-- global keybindings that were specified in register_buttons. +-- If keytable is not specified, then adds bindings to default +-- globalkeys table. If specified, then adds bindings to keytable and +-- returns it. +function awesompd:append_global_keys(keytable) + if keytable then + for i = 1, table.getn(self.global_bindings) do + keytable = awful.util.table.join(keytable, self.global_bindings[i]) + end + return keytable + else + for i = 1, table.getn(self.global_bindings) do + globalkeys = awful.util.table.join(globalkeys, self.global_bindings[i]) + end + end +end + +-- /// Group of mpc command functions /// + +-- Takes a command to mpc and a hook that is provided with awesompd +-- instance and the result of command execution. +function awesompd:command(com,hook) + local file = io.popen(self:mpcquery() .. com) + if hook then + hook(self,file) + end + file:close() +end + +-- Takes a command to mpc and read mode and returns the result. +function awesompd:command_read(com, mode) + mode = mode or "*line" + self:command(com, function(_, f) + result = f:read(mode) + end) + return result +end + +function awesompd:command_playpause() + return function() + self:command("toggle",self.update_track) + end +end + +function awesompd:command_next_track() + return function() + self:command("next",self.update_track) + end +end + +function awesompd:command_prev_track() + return function() + self:command("seek 0") + self:command("prev",self.update_track) + end +end + +function awesompd:command_stop() + return function() + self:command("stop",self.update_track) + end +end + +function awesompd:command_play_specific(n) + return function() + self:command("play " .. n,self.update_track) + end +end + +function awesompd:command_volume_up() + return function() + self:command("volume +5") + self:update_track() -- Nasty! I should replace it with proper callback later. + self:notify_state(self.NOTIFY_VOLUME) + end +end + +function awesompd:command_volume_down() + return function() + self:command("volume -5") + self:update_track() + self:notify_state(self.NOTIFY_VOLUME) + end +end + +function awesompd:command_load_playlist(name) + return function() + self:command("load " .. name, function() + self.recreate_menu = true + end) + end +end + +function awesompd:command_replace_playlist(name) + return function() + self:command("clear") + self:command("load " .. name) + self:command("play 1", self.update_track) + end +end + +function awesompd:command_clear_playlist() + return function() + self:command("clear", self.update_track) + self.recreate_list = true + self.recreate_menu = true + end +end + +function awesompd:command_open_in_browser(link) + return function() + if self.browser then + awful.util.spawn(self.browser .. " '" .. link .. "'") + end + end +end + +-- /// End of mpc command functions /// + +-- /// Menu generation functions /// + +function awesompd:command_show_menu() + return + function() + self:remove_hint() + if self.recreate_menu then + local new_menu = {} + if self.main_menu ~= nil then + self.main_menu:hide() + end + if self.status ~= awesompd.DISCONNECTED + then + self:check_list() + self:check_playlists() + local jamendo_menu = { { "Search by", + { { "Nothing (Top 100)", self:menu_jamendo_top() }, + { "Artist", self:menu_jamendo_search_by(jamendo.SEARCH_ARTIST) }, + { "Album", self:menu_jamendo_search_by(jamendo.SEARCH_ALBUM) }, + { "Tag", self:menu_jamendo_search_by(jamendo.SEARCH_TAG) }}} } + local browse_menu = self:menu_jamendo_browse() + if browse_menu then + table.insert(jamendo_menu, browse_menu) + end + table.insert(jamendo_menu, self:menu_jamendo_format()) + table.insert(jamendo_menu, self:menu_jamendo_order()) + + new_menu = { { "Playback", self:menu_playback() }, + { "Options", self:menu_options() }, + { "List", self:menu_list() }, + { "Playlists", self:menu_playlists() }, + { "Jamendo", jamendo_menu } } + end + table.insert(new_menu, { "Servers", self:menu_servers() }) + self.main_menu = awful.menu({ items = new_menu, width = 300 }) + self.recreate_menu = false + end + self.main_menu:toggle() + end +end + +-- Returns an icon for a checkbox menu item if it is checked, nil +-- otherwise. +function awesompd:menu_item_toggle(checked) + return checked and self.ICONS.CHECK or nil +end + +-- Returns an icon for a radiobox menu item if it is selected, nil +-- otherwise. +function awesompd:menu_item_radio(selected) + return selected and self.ICONS.RADIO or nil +end + +-- Returns the playback menu. Menu contains of: +-- Play\Pause - always +-- Previous - if the current track is not the first +-- in the list and playback is not stopped +-- Next - if the current track is not the last +-- in the list and playback is not stopped +-- Stop - if the playback is not stopped +-- Clear playlist - always +function awesompd:menu_playback() + if self.recreate_playback then + local new_menu = {} + table.insert(new_menu, { "Play\\Pause", + self:command_toggle(), + self.ICONS.PLAY_PAUSE }) + if self:playing_or_paused() then + if self.list_array and self.list_array[self.current_number-1] then + table.insert(new_menu, + { "Prev: " .. + awesompd.protect_string(jamendo.replace_link( + self.list_array[self.current_number - 1]), + true), + self:command_prev_track(), self.ICONS.PREV }) + end + if self.list_array and self.current_number ~= table.getn(self.list_array) then + table.insert(new_menu, + { "Next: " .. + awesompd.protect_string(jamendo.replace_link( + self.list_array[self.current_number + 1]), + true), + self:command_next_track(), self.ICONS.NEXT }) + end + table.insert(new_menu, { "Stop", self:command_stop(), self.ICONS.STOP }) + table.insert(new_menu, { "", nil }) + end + table.insert(new_menu, { "Clear playlist", self:command_clear_playlist() }) + self.recreate_playback = false + playback_menu = new_menu + end + return playback_menu +end + +-- Returns the current playlist menu. Menu consists of all elements in the playlist. +function awesompd:menu_list() + if self.recreate_list then + local new_menu = {} + if self.list_array then + local total_count = table.getn(self.list_array) + local start_num = (self.current_number - 15 > 0) and self.current_number - 15 or 1 + local end_num = (self.current_number + 15 < total_count ) and self.current_number + 15 or total_count + for i = start_num, end_num do + table.insert(new_menu, { jamendo.replace_link(self.list_array[i]), + self:command_play_specific(i), + self.current_number == i and + (self.status == self.PLAYING and self.ICONS.PLAY or self.ICONS.PAUSE) + or nil} ) + end + end + self.recreate_list = false + self.list_menu = new_menu + end + return self.list_menu +end + +-- Returns the playlists menu. Menu consists of all files in the playlist folder. +function awesompd:menu_playlists() + if self.recreate_playlists then + local new_menu = {} + if table.getn(self.playlists_array) > 0 then + for i = 1, table.getn(self.playlists_array) do + local submenu = {} + submenu[1] = { "Add to current", self:command_load_playlist(self.playlists_array[i]) } + submenu[2] = { "Replace current", self:command_replace_playlist(self.playlists_array[i]) } + new_menu[i] = { self.playlists_array[i], submenu } + end + table.insert(new_menu, {"", ""}) -- This is a separator + end + table.insert(new_menu, { "Refresh", function() self:check_playlists() end }) + self.recreate_playlists = false + self.playlists_menu = new_menu + end + return self.playlists_menu +end + +-- Returns the server menu. Menu consists of all servers specified by user during initialization. +function awesompd:menu_servers() + if self.recreate_servers then + local new_menu = {} + for i = 1, table.getn(self.servers) do + table.insert(new_menu, {"Server: " .. self.servers[i].server .. + ", port: " .. self.servers[i].port, + function() self:change_server(i) end, + self:menu_item_radio(i == self.current_server)}) + end + self.servers_menu = new_menu + end + return self.servers_menu +end + +-- Returns the options menu. Menu works like checkboxes for it's elements. +function awesompd:menu_options() + if self.recreate_options then + local new_menu = { { "Repeat", self:menu_toggle_repeat(), + self:menu_item_toggle(self.state_repeat == "on")}, + { "Random", self:menu_toggle_random(), + self:menu_item_toggle(self.state_random == "on")}, + { "Single", self:menu_toggle_single(), + self:menu_item_toggle(self.state_single == "on")}, + { "Consume", self:menu_toggle_consume(), + self:menu_item_toggle(self.state_consume == "on")} } + self.options_menu = new_menu + self.recreate_options = false + end + return self.options_menu +end + +function awesompd:menu_toggle_random() + return function() + self:command("random",self.update_track) + self:notify_state(self.NOTIFY_RANDOM) + end +end + +function awesompd:menu_toggle_repeat() + return function() + self:command("repeat",self.update_track) + self:notify_state(self.NOTIFY_REPEAT) + end +end + +function awesompd:menu_toggle_single() + return function() + self:command("single",self.update_track) + self:notify_state(self.NOTIFY_SINGLE) + end +end + +function awesompd:menu_toggle_consume() + return function() + self:command("consume",self.update_track) + self:notify_state(self.NOTIFY_CONSUME) + end +end + +function awesompd:menu_jamendo_top() + return + function () + local track_table = jamendo.return_track_table() + if not track_table then + self:add_hint("Can't connect to Jamendo server", "Please check your network connection") + else + self:add_jamendo_tracks(track_table) + self:add_hint("Jamendo Top 100 by " .. + jamendo.current_request_table.params.order.short_display, + format("Added %s tracks to the playlist", + table.getn(track_table))) + end + end +end + +function awesompd:menu_jamendo_format() + if self.recreate_jamendo_formats then + local setformat = + function(format) + return function() + jamendo.set_current_format(format) + self.recreate_menu = true + self.recreate_jamendo_formats = true + end + end + + local iscurr = + function(f) + return jamendo.current_request_table.params.streamencoding.value + == f.value + end + + local new_menu = {} + for _, format in pairs(jamendo.ALL_FORMATS) do + table.insert(new_menu, { format.display, setformat(format), + self:menu_item_radio(iscurr(format))}) + end + self.recreate_jamendo_formats = false + self.jamendo_formats_menu = { + "Format: " .. + jamendo.current_request_table.params.streamencoding.short_display, + new_menu } + end + return self.jamendo_formats_menu +end + +function awesompd:menu_jamendo_browse() + if self.recreate_jamendo_browse and self.browser + and self.current_track.unique_name then + local track = jamendo.get_track_by_link(self.current_track.unique_name) + local new_menu + if track then + local artist_link = + "http://www.jamendo.com/artist/" .. track.artist_link_name + local album_link = + "http://www.jamendo.com/album/" .. track.album_id + new_menu = { { "Artist's page" , + self:command_open_in_browser(artist_link) }, + { "Album's page" , + self:command_open_in_browser(album_link) } } + self.jamendo_browse_menu = { "Browse on Jamendo", new_menu } + else + self.jamendo_browse_menu = nil + end + end + return self.jamendo_browse_menu +end + +function awesompd:menu_jamendo_order() + if self.recreate_jamendo_order then + local setorder = + function(order) + return function() + jamendo.set_current_order(order) + self.recreate_menu = true + self.recreate_jamendo_order = true + end + end + + local iscurr = + function(o) + return jamendo.current_request_table.params.order.value + == o.value + end + + local new_menu = {} + for _, order in pairs(jamendo.ALL_ORDERS) do + table.insert(new_menu, { order.display, setorder(order), + self:menu_item_radio(iscurr(order))}) + end + self.recreate_jamendo_order = false + self.jamendo_order_menu = { + "Order: " .. + jamendo.current_request_table.params.order.short_display, + new_menu } + end + return self.jamendo_order_menu +end + +function awesompd:menu_jamendo_search_by(what) + return function() + local callback = + function(s) + local result = jamendo.search_by(what, s) + if result then + local track_count = table.getn(result.tracks) + self:add_jamendo_tracks(result.tracks) + self:add_hint(format("%s \"%s\" was found", + what.display, + result.search_res.name), + format("Added %s tracks to the playlist", + track_count)) + else + self:add_hint("Search failed", + format("%s \"%s\" was not found", + what.display, s)) + end + end + self:display_inputbox("Search music on Jamendo", + what.display, callback) + end +end + +-- Checks if the current playlist has changed after the last check. +function awesompd:check_list() + local bus = io.popen(self:mpcquery() .. "playlist") + local info = bus:read("*all") + bus:close() + if info ~= self.list_line then + self.list_line = info + if string.len(info) > 0 then + self.list_array = self.split(string.sub(info,1,string.len(info))) + else + self.list_array = {} + end + self.recreate_menu = true + self.recreate_list = true + end +end + +-- Checks if the collection of playlists changed after the last check. +function awesompd:check_playlists() + local bus = io.popen(self:mpcquery() .. "lsplaylists") + local info = bus:read("*all") + bus:close() + if info ~= self.playlists_line then + self.playlists_line = info + if string.len(info) > 0 then + self.playlists_array = self.split(info) + else + self.playlists_array = {} + end + self.recreate_menu = true + self.recreate_playlists = true + end +end + +-- Changes the current server to the specified one. +function awesompd:change_server(server_number) + self.current_server = server_number + self:remove_hint() + self.recreate_menu = true + self.recreate_playback = true + self.recreate_list = true + self.recreate_playlists = true + self.recreate_servers = true + self:update_track() +end + +function awesompd:add_jamendo_tracks(track_table) + for i = 1,table.getn(track_table) do + self:command("add '" .. string.gsub(track_table[i].stream, '\\/', '/') .. "'") + end + self.recreate_menu = true + self.recreate_list = true +end + +-- /// End of menu generation functions /// + +function awesompd:add_hint(hint_title, hint_text, hint_image) + self:remove_hint() + self.notification = naughty.notify({ title = hint_title + , text = awesompd.protect_string(hint_text) + , timeout = 5 + , position = "top_right" + , icon = hint_image + , icon_size = self.album_cover_size + }) +end + +function awesompd:remove_hint() + if self.notification ~= nil then + naughty.destroy(self.notification) + self.notification = nil + end +end + +function awesompd:notify_track() + if self:playing_or_paused() then + local caption = self.status_text + local nf_text = self.get_display_name(self.current_track) + local al_cover = nil + if self.show_album_cover then + nf_text = self.get_extended_info(self.current_track) + al_cover = self.current_track.album_cover + end + self:add_hint(caption, nf_text, al_cover) + end +end + +function awesompd:notify_state(state_changed) + state_array = { "Volume: " .. self.state_volume , + "Repeat: " .. self.state_repeat , + "Random: " .. self.state_random , + "Single: " .. self.state_single , + "Consume: " .. self.state_consume } + state_header = state_array[state_changed] + table.remove(state_array,state_changed) + full_state = state_array[1] + for i = 2, table.getn(state_array) do + full_state = full_state .. "\n" .. state_array[i] + end + self:add_hint(state_header, full_state) +end + +function awesompd:wrap_output(text) + return format('%s%s%s', + self.font, self.ldecorator, + awesompd.protect_string(text), self.rdecorator) +end + +function awesompd:mpcquery() + return "mpc -h " .. self.servers[self.current_server].server .. + " -p " .. self.servers[self.current_server].port .. " " +end + +-- This function actually sets the text on the widget. +function awesompd:set_text(text) + self.widget.text = self:wrap_output(text) +end + +function awesompd.find_pattern(text, pattern, start) + return utf8sub(text, string.find(text, pattern, start)) +end + +-- Scroll the given text by the current number of symbols. +function awesompd:scroll_text(text) + local result = text + if self.scrolling then + if self.output_size < utf8len(text) then + text = text .. " - " + if self.scroll_pos + self.output_size - 1 > utf8len(text) then + result = utf8sub(text, self.scroll_pos) + result = result .. utf8sub(text, 1, self.scroll_pos + self.output_size - 1 - utf8len(text)) + self.scroll_pos = self.scroll_pos + 1 + if self.scroll_pos > utf8len(text) then + self.scroll_pos = 1 + end + else + result = utf8sub(text, self.scroll_pos, self.scroll_pos + self.output_size - 1) + self.scroll_pos = self.scroll_pos + 1 + end + end + else + if self.output_size < utf8len(text) then + result = utf8sub(text, 1, self.output_size) + end + end + return result +end + +-- This function is called every second. +function awesompd:update_widget() + self:set_text(self:scroll_text(self.text)) + self:check_notify() +end + +-- This function is called by update_track each time content of +-- the widget must be changed. +function awesompd:update_widget_text() + if self:playing_or_paused() then + self.text = self.get_display_name(self.current_track) + else + self.text = self.status + end +end + +-- Checks if notification should be shown and shows if positive. +function awesompd:check_notify() + if self.to_notify then + self:notify_track() + self.to_notify = false + end +end + +function awesompd:notify_connect() + self:add_hint("Connected", "Connection established to " .. self.servers[self.current_server].server .. + " on port " .. self.servers[self.current_server].port) +end + +function awesompd:notify_disconnect() + self:add_hint("Disconnected", "Cannot connect to " .. self.servers[self.current_server].server .. + " on port " .. self.servers[self.current_server].port) +end + +function awesompd:update_track(file) + local file_exists = (file ~= nil) + if not file_exists then + file = io.popen(self:mpcquery()) + end + local track_line = file:read("*line") + local status_line = file:read("*line") + local options_line = file:read("*line") + if not file_exists then + file:close() + end + + if not track_line or string.len(track_line) == 0 then + if self.status ~= awesompd.DISCONNECTED then + self:notify_disconnect() + self.recreate_menu = true + self.status = awesompd.DISCONNECTED + self.current_track = { } + self:update_widget_text() + end + else + if self.status == awesompd.DISCONNECTED then + self:notify_connect() + self.recreate_menu = true + self:update_widget_text() + end + if string.find(track_line,"volume:") or string.find(track_line,"Updating DB") then + if self.status ~= awesompd.STOPPED then + self.status = awesompd.STOPPED + self.current_number = 0 + self.recreate_menu = true + self.recreate_playback = true + self.recreate_list = true + self.album_cover = nil + self.current_track = { } + self:update_widget_text() + end + self:update_state(track_line) + else + self:update_state(options_line) + local _, _, new_file, new_album = + string.find(self:command_read('current -f "%file%-<>-%album%"', "*line"), "(.+)%-<>%-(.*)") + if new_file ~= self.current_track.unique_name then + self.current_track = jamendo.get_track_by_link(new_file) + if not self.current_track then + self.current_track = { display_name = track_line, + album_name = new_album } + end + self.current_track.unique_name = new_file + if self.show_album_cover then + self.current_track.album_cover = self:get_cover(new_file) + end + self.to_notify = true + self.recreate_menu = true + self.recreate_playback = true + self.recreate_list = true + self.current_number = tonumber(self.find_pattern(status_line,"%d+")) + self:update_widget_text() + + -- If the track is not the last, asynchronously download + -- the cover for the next track. + if self.list_array and self.current_number ~= table.getn(self.list_array) then + -- Get the link (in case it is Jamendo stream) to the next track + local next_track = + self:command_read('playlist -f "%file%" | head -' .. + self.current_number + 1 .. ' | tail -1', "*line") + jamendo.try_get_cover_async(next_track) + end + end + local tmp_pst = string.find(status_line,"%d+%:%d+%/") + local progress = self.find_pattern(status_line,"%#%d+/%d+") .. " " .. string.sub(status_line,tmp_pst) + local new_status = awesompd.PLAYING + if string.find(status_line,"paused") then + new_status = awesompd.PAUSED + end + if new_status ~= self.status then + self.to_notify = true + self.recreate_list = true + self.status = new_status + self:update_widget_text() + end + self.status_text = self.status .. " " .. progress + end + end +end + +function awesompd:update_state(state_string) + self.state_volume = self.find_pattern(state_string,"%d+%% ") + if string.find(state_string,"repeat: on") then + self.state_repeat = self:check_set_state(self.state_repeat, "on") + else + self.state_repeat = self:check_set_state(self.state_repeat, "off") + end + if string.find(state_string,"random: on") then + self.state_random = self:check_set_state(self.state_random, "on") + else + self.state_random = self:check_set_state(self.state_random, "off") + end + if string.find(state_string,"single: on") then + self.state_single = self:check_set_state(self.state_single, "on") + else + self.state_single = self:check_set_state(self.state_single, "off") + end + if string.find(state_string,"consume: on") then + self.state_consume = self:check_set_state(self.state_consume, "on") + else + self.state_consume = self:check_set_state(self.state_consume, "off") + end +end + +function awesompd:check_set_state(statevar, val) + if statevar ~= val then + self.recreate_menu = true + self.recreate_options = true + end + return val +end + +function awesompd:run_prompt(welcome,hook) + awful.prompt.run({ prompt = welcome }, + self.promptbox[mouse.screen].widget, + hook) +end + +-- Replaces control characters with escaped ones. +-- for_menu - defines if the special escable table for menus should be +-- used. +function awesompd.protect_string(str, for_menu) + if for_menu then + return utf8replace(str, awesompd.ESCAPE_MENU_SYMBOL_MAPPING) + else + return utf8replace(str, awesompd.ESCAPE_SYMBOL_MAPPING) + end +end + +-- Displays an inputbox on the screen (looks like naughty with prompt). +-- title_text - bold text on the first line +-- prompt_text - preceding text on the second line +-- hook - function that will be called with input data +-- Use it like this: +-- self:display_inputbox("Search music on Jamendo", "Artist", print) +function awesompd:display_inputbox(title_text, prompt_text, hook) + if self.inputbox then -- Inputbox already exists, replace it + keygrabber.stop() + self.inputbox.screen = nil + self.inputbox = nil + end + local width = 200 + local height = 30 + local border_color = beautiful.bg_focus or '#535d6c' + local margin = 5 + local wbox = wibox({ name = "awmpd_ibox", height = height , width = width, + border_color = border_color, border_width = 1 }) + self.inputbox = wbox + local ws = screen[mouse.screen].workarea + + wbox:geometry({ x = ws.width - width - 5, y = 25 }) + wbox.screen = mouse.screen + wbox.ontop = true + + local exe_callback = function(s) + hook(s) + wbox.screen = nil + self.inputbox = nil + end + local done_callback = function() + wbox.screen = nil + self.inputbox = nil + end + local wprompt = awful.widget.prompt({ layout = awful.widget.layout.horizontal.leftright }) + local wtbox = widget({ type = "textbox" }) + wtbox:margin({ right = margin, left = margin, bottom = margin, top = margin }) + wtbox.text = "" .. title_text .. "" + wbox.widgets = { wtbox, wprompt, layout = awful.widget.layout.vertical.flex } + awful.prompt.run( { prompt = " " .. prompt_text .. ": " }, wprompt.widget, + exe_callback, nil, nil, nil, done_callback) +end + +-- Gets the cover for the given track. First looks in the Jamendo +-- cache. If the track is not a Jamendo stream, looks in local +-- folders. If there is no cover art either returns the default album +-- cover. +function awesompd:get_cover(track) + return jamendo.try_get_cover(track) or + self:try_get_local_cover() or self.ICONS.DEFAULT_ALBUM_COVER +end + +-- Tries to find an album cover for the track that is currently +-- playing. +function awesompd:try_get_local_cover() + if self.mpd_config then + local result + -- First find the music directory in MPD configuration file + local _, _, music_folder = string.find( + self.pread('cat ' .. self.mpd_config .. ' | grep -v "#" | grep music_directory', "*line"), + 'music_directory%s+"(.+)"') + music_folder = music_folder .. "/" + + -- If the music_folder is specified with ~ at the beginning, + -- replace it with user home directory + if string.sub(music_folder, 1, 1) == "~" then + local user_folder = self.pread("echo ~", "*line") + music_folder = user_folder .. string.sub(music_folder, 2) + end + + -- Get the path to the file currently playing. + local current_file = self:command_read('current -f "%file%"') + local _, _, current_file_folder = string.find(current_file, '(.+%/).*') + + -- Check if the current file is not some kind of http stream or + -- Spotify track (like spotify:track:5r65GeuIoebfJB5sLcuPoC) + if not current_file_folder or string.match(current_file, "%w+://") then + return -- Let the default image to be the cover + end + + local folder = music_folder .. current_file_folder + + -- Get all images in the folder. Also escape occasional single + -- quotes in folder name. + local request = format("ls '%s' | grep -P '\.jpg\|\.png\|\.gif|\.jpeg'", + string.gsub(folder, "'", "'\\''")) + + local covers = self.pread(request, "*all") + local covers_table = self.split(covers) + + if covers_table.n > 0 then + result = folder .. covers_table[1] + if covers_table.n > 1 then + -- Searching for front cover with grep because Lua regular + -- expressions suck:[ + local front_cover = + self.pread('echo "' .. covers .. + '" | grep -i "cover\|front\|folder\|albumart" | head -n 1', "*line") + if front_cover then + result = folder .. front_cover + end + end + end + return result + end +end + +-- /// Deprecated, left for some backward compatibility in +-- configuration /// + +function awesompd:command_toggle() + return self:command_playpause() +end diff --git a/awesompd/icons/check_icon.png b/awesompd/icons/check_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dc5cba1287277f859cece13ee824cff04505493c GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*x37`TN&n2}-D90{Nxdx@v7EBh@j2|->Fp+6J-fkKict`Q~9`MJ5N zc_j?aMX8A;sVNHOnI#zt?w-B@;f;LaKt(>DE{-7|)Wq;QBO>@b3z`km+8Rs&h4)nzUcX~};;`b{3&)VX%&B)4i;3(^&${ye bya{8{KVI2gj>ldA&1LX(^>bP0l+XkK2sudM literal 0 HcmV?d00001 diff --git a/awesompd/icons/default_album_cover.png b/awesompd/icons/default_album_cover.png new file mode 100644 index 0000000000000000000000000000000000000000..b952ba4c1b323889c9a7ad2f0da2106159df7b0c GIT binary patch literal 18940 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 literal 0 HcmV?d00001 diff --git a/awesompd/icons/play_icon.png b/awesompd/icons/play_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..83230d13214f288943c4de91d96d8b74a64251ee GIT binary patch literal 234 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*x37`TN&n2}-D90{Nxdx@v7EBh@j2|-S?#UDi11BE0@Tq8=H^K)}k z^GX<;i&7IyQd1PlGfOfQ+&z5*!W;R-fr?x`T^vI!PAC8Q|KFZjwV|`o$)S|_pR?gn zJ%w8ePH_qu0vi(>E-M5v3m#VJN^Wd*`P%bD>VU^OZi$m$nUr@X9@!W8oMb$J3(S$PA gs7>V_fBw&8`!-it+JEQ0KOj3jUHx3vIVCg!09CF_#Q*>R literal 0 HcmV?d00001 diff --git a/awesompd/icons/prev_icon.png b/awesompd/icons/prev_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8e1638154d8920e2ae45ab1205c8a4ce620aad79 GIT binary patch literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*x37`TN&n2}-D90{Nxdx@v7EBkFONggKMwe^kJKq1-8kcblJ{M_8s zyb>Unfx)>bHL)Z$MWH;iBtya7(>EYRFO?lA?(XU07-Dfc`Op9V_ROjcosCWoq0IlB z4XqAnyPaTV;Z0fOVr<9U@s%r#x#OD8=8P1TAfENy9cC=^#XF2z0we@XS_)sJt1;B= V6%q|+=Gh6d%hT1*Wt~$(697J!Kd}G+ literal 0 HcmV?d00001 diff --git a/awesompd/icons/radio_icon.png b/awesompd/icons/radio_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ffdf065b18858f0544eba1abcf47f9f9723cd9cb GIT binary patch literal 342 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*x37`TN&n2}-D90{Nxdx@v7EBh@j2|+Pw?NYD~$r9Iy66gHf+|;}h z2Ir#G#FEq$h4Rdj3D4{CS0 zm#Q37; z`#pcjcWX5L63c{N^B<3x&NE?4$rfkFEL&z+*guix)i Zu$#+N94XdKsRcTo!PC{xWt~$(699`AapV91 literal 0 HcmV?d00001 diff --git a/awesompd/icons/stop_icon.png b/awesompd/icons/stop_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6bef770ae510991bee43a7c9095ba3259017d5cd GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*x37`TN&n2}-D90{Nxdx@v7EBh@j2|i90cjJatKq1Kz*N775{M_8s zyb=cIqSVBa)D(sC%#sWRcTeAd@J2pypdwvQ7sn8b)5(AS|F>sWZRl)ta@feIlTowi u&*2j%PH=qwb)Y)&N<(5I!{ZC*eHauzb4hE=4%`LQ$l&Sf=d#Wzp$Pyv^gOEo literal 0 HcmV?d00001 diff --git a/awesompd/jamendo.lua b/awesompd/jamendo.lua new file mode 100644 index 0000000..9fa0428 --- /dev/null +++ b/awesompd/jamendo.lua @@ -0,0 +1,524 @@ +--------------------------------------------------------------------------- +-- @author Alexander Yakushev +-- @copyright 2011 Alexander Yakushev +-- @release v1.1.5 +--------------------------------------------------------------------------- + +-- Grab environment +local os = os +local awful = awful +local string = string +local table = table +local io = io +local pairs = pairs +local type = type +local assert = assert +local print = print +local tonumber = tonumber +local math = math +local tostring = tostring +local asyncshell = asyncshell + +module('jamendo') + +-- UTILITY STUFF +-- Checks whether file specified by filename exists. +local function file_exists(filename, mode) + mode = mode or 'r' + f = io.open(filename, mode) + if f then + f:close() + return true + else + return false + end +end + +-- Global variables +FORMAT_MP3 = { display = "MP3 (128k)", + short_display = "MP3", + value = "mp31" } +FORMAT_OGG = { display = "Ogg Vorbis (q4)", + short_display = "Ogg", + value = "ogg2" } +ORDER_RATINGDAILY = { display = "Daily rating", + short_display = "daily rating", + value = "ratingday_desc" } +ORDER_RATINGWEEKLY = { display = "Weekly rating", + short_display = "weekly rating", + value = "ratingweek_desc" } +ORDER_RATINGTOTAL = { display = "All time rating", + short_display = "all time rating", + value = "ratingtotal_desc" } +ORDER_RANDOM = { display = "Random", + short_display = "random", + value = "random_desc" } +ORDER_RELEVANCE = { display = "None (consecutive)", + short_display = "none", + value = "searchweight_desc" } +SEARCH_ARTIST = { display = "Artist", + unit = "artist", + value = "artist_id" } +SEARCH_ALBUM = { display = "Album", + unit = "album", + value = "album_id" } +SEARCH_TAG = { display = "Tag", + unit = "tag", + value = "tag_id" } +ALL_FORMATS = { FORMAT_MP3, FORMAT_OGG } +ALL_ORDERS = { ORDER_RELEVANCE, ORDER_RANDOM, ORDER_RATINGDAILY, + ORDER_RATINGWEEKLY, ORDER_RATINGTOTAL } + +current_request_table = { unit = "track", + fields = {"id", "artist_url", "artist_name", "name", + "stream", "album_image", "album_name" }, + joins = { "track_album", "album_artist" }, + params = { streamencoding = FORMAT_MP3, + order = ORDER_RATINGWEEKLY, + n = 100 }} + +-- Local variables +local jamendo_list = {} +local cache_file = awful.util.getdir ("cache").."/jamendo_cache" +local cache_header = "[version=1.1.0]" +local album_covers_folder = awful.util.getdir("cache") .. "/jamendo_covers/" +local default_mp3_stream = nil +local search_template = { fields = { "id", "name" }, + joins = {}, + params = { order = ORDER_RELEVANCE, + n = 1}} + +-- DEPRECATED. Will be removed in the next major release. +-- Returns default stream number for MP3 format. Requests API for it +-- not more often than every hour. +local function get_default_mp3_stream() + if not default_mp3_stream or + (os.time() - default_mp3_stream.last_checked) > 3600 then + local trygetlink = + perform_request("echo $(curl -w %{redirect_url} " .. + "'http://api.jamendo.com/get2/stream/track/redirect/" .. + "?streamencoding="..FORMAT_MP3.value.."&id=729304')") + local _, _, prefix = string.find(trygetlink,"stream(%d+)\.jamendo\.com") + default_mp3_stream = { id = prefix, last_checked = os.time() } + end + return default_mp3_stream.id +end + +-- Returns the track ID from the given link to Jamendo stream. If the +-- given text is not the Jamendo stream returns nil. +function get_id_from_link(link) + local _, _, id = string.find(link,"storage%-new.newjamendo.com%?trackid=(%d+)") + return id +end + +-- Returns link to music stream for the given track ID. Uses MP3 +-- format and the default stream for it. +local function get_link_by_id(id) + -- This function is subject to change in the future. + return string.format("http://storage-new.newjamendo.com?trackid=%s&format=mp31&u=0", id) +end + +-- -- Returns the album id for given music stream. +-- function get_album_id_by_link(link) +-- local id = get_id_from_link(link, true) +-- if id and jamendo_list[id] then +-- return jamendo_list[id].album_id +-- end +-- end + +-- Returns the track table for the given music stream. +function get_track_by_link(link) + local id = get_id_from_link(link, true) + if id and jamendo_list[id] then + return jamendo_list[id] + end +end + +-- If a track is actually a Jamendo stream, replace it with normal +-- track name. +function replace_link(track_name) + local track = get_track_by_link(track_name) + if track then + return track.display_name + else + return track_name + end +end + +-- Returns table of track IDs, names and other things based on the +-- request table. +function return_track_table(request_table) + local req_string = form_request(request_table) + local response = perform_request(req_string) + if not response then + return nil -- Bad internet connection + end + parse_table = parse_json(response) + for i = 1, table.getn(parse_table) do + if parse_table[i].stream == "" then + -- Some songs don't have Ogg stream, use MP3 instead + parse_table[i].stream = get_link_by_id(parse_table[i].id) + end + _, _, parse_table[i].artist_link_name = + string.find(parse_table[i].artist_url, "\\/artist\\/(.+)") + -- Remove Jamendo escape slashes + parse_table[i].artist_name = + string.gsub(parse_table[i].artist_name, "\\/", "/") + parse_table[i].name = string.gsub(parse_table[i].name, "\\/", "/") + + parse_table[i].display_name = + parse_table[i].artist_name .. " - " .. parse_table[i].name + -- Do Jamendo a favor, extract album_id for the track yourself + -- from album_image link :) + local _, _, album_id = + string.find(parse_table[i].album_image, "\\/(%d+)\\/covers") + parse_table[i].album_id = album_id or 0 + -- Save fetched tracks for further caching + jamendo_list[parse_table[i].id] = parse_table[i] + end + save_cache() + return parse_table +end + +-- Generates the request to Jamendo API based on provided request +-- table. If request_table is nil, uses current_request_table instead. +-- For all values that do not exist in request_table use ones from +-- current_request_table. +-- return - HTTP-request +function form_request(request_table) + local curl_str = "curl -A 'Mozilla/4.0' -fsm 5 \"%s\"" + local url = "http://api.jamendo.com/en/?m=get2%s%s" + request_table = request_table or current_request_table + + local fields = request_table.fields or current_request_table.fields + local joins = request_table.joins or current_request_table.joins + local unit = request_table.unit or current_request_table.unit + + -- Form field&joins string (like field1+field2+fieldN%2Fjoin+) + local fnj_string = "&m_params=" + for i = 1, table.getn(fields) do + fnj_string = fnj_string .. fields[i] .. "+" + end + fnj_string = string.sub(fnj_string,1,string.len(fnj_string)-1) + + fnj_string = fnj_string .. "%2F" .. unit .. "%2Fjson%2F" + for i = 1, table.getn(joins) do + fnj_string = fnj_string .. joins[i] .. "+" + end + fnj_string = fnj_string .. "%2F" + + local params = {} + -- If parameters where supplied in request_table, add them to the + -- parameters in current_request_table. + if request_table.params and + request_table.params ~= current_request_table.params then + -- First fill params with current_request_table parameters + for k, v in pairs(current_request_table.params) do + params[k] = v + end + -- Then add and overwrite them with request_table parameters + for k, v in pairs(request_table.params) do + params[k] = v + end + else -- Or just use current_request_table.params + params = current_request_table.params + end + -- Form parameter string (like param1=value1¶m2=value2) + local param_string = "" + for k, v in pairs(params) do + if type(v) == "table" then + v = v.value + end + v = string.gsub(v, " ", "+") + param_string = param_string .. "&" .. k .. "=" .. v + end + + return string.format(curl_str, string.format(url, fnj_string, param_string)) +end + +-- Primitive function for parsing Jamendo API JSON response. Does not +-- support arrays. Supports only strings and numbers as values. +-- Provides basic safety (correctly handles special symbols like comma +-- and curly brackets inside strings) +-- text - JSON text +function parse_json(text) + local parse_table = {} + local block = {} + local i = 0 + local inblock = false + local instring = false + local curr_key = nil + local curr_val = nil + while i and i < string.len(text) do + if not inblock then -- We are not inside the block, find next { + i = string.find(text, "{", i+1) + inblock = true + block = {} + else + if not curr_key then -- We haven't found key yet + if not instring then -- We are not in string, check for more tags + local j = string.find(text, '"', i+1) + local k = string.find(text, '}', i+1) + if j and j < k then -- There are more tags in this block + i = j + instring = true + else -- Block is over, we found its ending + i = k + inblock = false + table.insert(parse_table, block) + end + else -- We are in string, find its ending + _, i, curr_key = string.find(text,'(.-[^%\\])"', i+1) + instring = false + end + else -- We have the key, let's find the value + if not curr_val then -- Value is not found yet + if not instring then -- Not in string, check if value is string + local j = string.find(text, '"', i+1) + local k = string.find(text, '[,}]', i+1) + if j and j < k then -- Value is string + i = j + instring = true + else -- Value is int + _, i, curr_val = string.find(text,'(%d+)', i+1) + end + else -- We are in string, find its ending + local j = string.find(text, '"', i+1) + if j == i+1 then -- String is empty + i = j + curr_val = "" + else + _, i, curr_val = string.find(text,'(.-[^%\\])"', i+1) + curr_val = utf8_codes_to_symbols(curr_val) + end + instring = false + end + else -- We have both key and value, add it to table + block[curr_key] = curr_val + curr_key = nil + curr_val = nil + end + end + end + end + return parse_table +end + +-- Jamendo returns Unicode symbols as \uXXXX. Lua does not transform +-- them into symbols so we need to do it ourselves. +function utf8_codes_to_symbols (s) + local hexnums = "[%dabcdefABCDEF]" + local pattern = string.format("\\u(%s%s%s%s?)", + hexnums, hexnums, hexnums, hexnums) + local decode = function(code) + code = tonumber(code, 16) + if code < 128 then -- one-byte symbol + return string.char(code) + elseif code < 2048 then -- two-byte symbol + -- Grab high and low bytes + local hi = math.floor(code / 64) + local lo = math.mod(code, 64) + -- Return symbol as \hi\lo + return string.char(hi + 192, lo + 128) + elseif code < 65536 then + -- Grab high, middle and low bytes + local hi = math.floor(code / 4096) + local leftover = code - hi * 4096 + local mi = math.floor(leftover / 64) + leftover = leftover - mi * 64 + local lo = math.mod(leftover, 64) + -- Return symbol as \hi\mi\lo + return string.char(hi + 224, mi + 160, lo + 128) + elseif code < 1114112 then + -- Grab high, highmiddle, lowmiddle and low bytes + local hi = math.floor(code / 262144) + local leftover = code - hi * 262144 + local hm = math.floor(leftover / 4096) + leftover = leftover - hm * 4096 + local lm = math.floor(leftover / 64) + local lo = math.mod(leftover, 64) + -- Return symbol as \hi\hm\lm\lo + return string.char(hi + 240, hm + 128, lm + 128, lo + 128) + else -- It is not Unicode symbol at all + return tostring(code) + end + end + return string.gsub(s, pattern, decode) +end + +-- Retrieves mapping of track IDs to track names and album IDs to +-- avoid redundant queries when Awesome gets restarted. +local function retrieve_cache() + local bus = io.open(cache_file) + local track = {} + if bus then + local header = bus:read("*line") + if header == cache_header then + for l in bus:lines() do + local _, _, id, artist_link_name, album_name, album_id, track_name = + string.find(l,"(%d+)-([^-]+)-([^-]+)-(%d+)-(.+)") + track = {} + track.id = id + track.artist_link_name = string.gsub(artist_link_name, '\\_', '-') + track.album_name = string.gsub(album_name, '\\_', '-') + track.album_id = album_id + track.display_name = track_name + jamendo_list[id] = track + end + else + -- We encountered an outdated version of the cache + -- file. Let's just remove it. + awful.util.spawn("rm -f " .. cache_file) + end + end +end + +-- Saves track IDs to track names and album IDs mapping into the cache +-- file. +function save_cache() + local bus = io.open(cache_file, "w") + bus:write(cache_header .. "\n") + for id,track in pairs(jamendo_list) do + bus:write(string.format("%s-%s-%s-%s-%s\n", id, + string.gsub(track.artist_link_name, '-', '\\_'), + string.gsub(track.album_name, '-', '\\_'), + track.album_id, track.display_name)) + end + bus:flush() + bus:close() +end + +-- Retrieve cache on initialization +retrieve_cache() + +-- Returns a filename of the album cover and formed wget request that +-- downloads the album cover for the given track name. If the album +-- cover already exists returns nil as the second argument. +function fetch_album_cover_request(track_id) + local track = jamendo_list[track_id] + local album_id = track.album_id + + if album_id == 0 then -- No cover for tracks without album! + return nil + end + local file_path = album_covers_folder .. album_id .. ".jpg" + + if not file_exists(file_path) then -- We need to download it + -- First check if cache directory exists + f = io.popen('test -d ' .. album_covers_folder .. ' && echo t') + if f:read("*line") ~= 't' then + awful.util.spawn("mkdir " .. album_covers_folder) + end + f:close() + + if not track.album_image then -- Wow! We have album_id, but + local a_id = tostring(album_id) --don't have album_image. Well, + local prefix = --it happens. + string.sub(a_id, 1, string.len(a_id) - 3) + track.album_image = + string.format("http://imgjam.com/albums/s%s/%s/covers/1.100.jpg", + prefix == "" and 0 or prefix, a_id) + end + + return file_path, string.format("wget %s -O %s 2> /dev/null", + track.album_image, file_path) + else -- Cover already downloaded, return its filename and nil + return file_path, nil + end +end + +-- Returns a file containing an album cover for given track id. First +-- searches in the cache folder. If file is not there, fetches it from +-- the Internet and saves into the cache folder. +function get_album_cover(track_id) + local file_path, fetch_req = fetch_album_cover_request(track_id) + if fetch_req then + local f = io.popen(fetch_req) + f:close() + + -- Let's check if file is finally there, just in case + if not file_exists(file_path) then + return nil + end + end + return file_path +end + +-- Same as get_album_cover, but downloads (if necessary) the cover +-- asynchronously. +function get_album_cover_async(track_id) + local file_path, fetch_req = fetch_album_cover_request(track_id) + if fetch_req then + asyncshell.request(fetch_req) + end +end + +-- Checks if track_name is actually a link to Jamendo stream. If true +-- returns the file with album cover for the track. +function try_get_cover(track_name) + local id = get_id_from_link(track_name) + if id then + return get_album_cover(id) + end +end + +-- Same as try_get_cover, but calls get_album_cover_async inside. +function try_get_cover_async(track_name) + local id = get_id_from_link(track_name) + if id then + return get_album_cover_async(id) + end +end + +-- Returns the track table for given query and search method. +-- what - search method - SEARCH_ARTIST, ALBUM or TAG +-- s - string to search +function search_by(what, s) + -- Get a default request and set unit and query + local req = search_template + req.unit = what.unit + req.params.searchquery = s + local resp = perform_request(form_request(req)) + if resp then + local search_res = parse_json(resp)[1] + + if search_res then + -- Now when we got the search result, find tracks filtered by + -- this result. + local params = {} + params[what.value] = search_res.id + req = { params = params } + local track_table = return_track_table(req) + return { search_res = search_res, tracks = track_table } + end + end +end + +-- Executes request_string with io.popen and returns the response. +function perform_request(reqest_string) + local bus = assert(io.popen(reqest_string,'r')) + local response = bus:read("*all") + bus:close() + -- Curl with popen can sometimes fail to fetch data when the + -- connection is slow. Let's try again if it fails. + if string.len(response) == 0 then + bus = assert(io.popen(reqest_string,'r')) + response = bus:read("*all") + bus:close() + -- If it still can't read anything, return nil + if string.len(response) ~= 0 then + return nil + end + end + return response +end + +-- Sets default streamencoding in current_request_table. +function set_current_format(format) + current_request_table.params.streamencoding = format +end + +-- Sets default order in current_request_table. +function set_current_order(order) + current_request_table.params.order = order +end diff --git a/awesompd/rcsample.lua b/awesompd/rcsample.lua new file mode 100644 index 0000000..c5e0364 --- /dev/null +++ b/awesompd/rcsample.lua @@ -0,0 +1,407 @@ +-- Standard awesome library +require("awful") +require("awful.autofocus") +require("awful.rules") +-- Theme handling library +require("beautiful") +-- Notification library +require("naughty") + +-- Load Debian menu entries +require("debian.menu") + +-- {{{ Variable definitions +-- Themes define colours, icons, and wallpapers +beautiful.init("/usr/share/awesome/themes/default/theme.lua") + +-- This is used later as the default terminal and editor to run. +terminal = "x-terminal-emulator" +editor = os.getenv("EDITOR") or "editor" +editor_cmd = terminal .. " -e " .. editor + +-- Default modkey. +-- Usually, Mod4 is the key with a logo between Control and Alt. +-- If you do not like this or do not have such a key, +-- I suggest you to remap Mod4 to another key using xmodmap or other tools. +-- However, you can use another modifier like Mod1, but it may interact with others. +modkey = "Mod4" + +-- Table of layouts to cover with awful.layout.inc, order matters. +layouts = +{ + awful.layout.suit.floating, + awful.layout.suit.tile, + awful.layout.suit.tile.left, + awful.layout.suit.tile.bottom, + awful.layout.suit.tile.top, + awful.layout.suit.fair, + awful.layout.suit.fair.horizontal, + awful.layout.suit.spiral, + awful.layout.suit.spiral.dwindle, + awful.layout.suit.max, + awful.layout.suit.max.fullscreen, + awful.layout.suit.magnifier +} +-- }}} + +-- {{{ Tags +-- Define a tag table which hold all screen tags. +tags = {} +for s = 1, screen.count() do + -- Each screen has its own tag table. + tags[s] = awful.tag({ 1, 2, 3, 4, 5, 6, 7, 8, 9 }, s, layouts[1]) +end +-- }}} + +-- {{{ Menu +-- Create a laucher widget and a main menu +myawesomemenu = { + { "manual", terminal .. " -e man awesome" }, + { "edit config", editor_cmd .. " " .. awful.util.getdir("config") .. "/rc.lua" }, + { "restart", awesome.restart }, + { "quit", awesome.quit } +} + +mymainmenu = awful.menu({ items = { { "awesome", myawesomemenu, beautiful.awesome_icon }, + { "Debian", debian.menu.Debian_menu.Debian }, + { "open terminal", terminal } + } + }) + +mylauncher = awful.widget.launcher({ image = image(beautiful.awesome_icon), + menu = mymainmenu }) +-- }}} + +-- {{{ Wibox +-- Create a textclock widget +mytextclock = awful.widget.textclock({ align = "right" }) + +-- Create a systray +mysystray = widget({ type = "systray" }) + +-- BEGIN OF AWESOMPD WIDGET DECLARATION + + require('awesompd/awesompd') + + musicwidget = awesompd:create() -- Create awesompd widget + musicwidget.font = "Liberation Mono" -- Set widget font + musicwidget.scrolling = true -- If true, the text in the widget will be scrolled + musicwidget.output_size = 30 -- Set the size of widget in symbols + musicwidget.update_interval = 10 -- Set the update interval in seconds + + -- Set the folder where icons are located (change username to your login name) + musicwidget.path_to_icons = "/home/username/.config/awesome/icons" + + -- Set the default music format for Jamendo streams. You can change + -- this option on the fly in awesompd itself. + -- possible formats: awesompd.FORMAT_MP3, awesompd.FORMAT_OGG + musicwidget.jamendo_format = awesompd.FORMAT_MP3 + + -- Specify the browser you use so awesompd can open links from + -- Jamendo in it. + musicwidget.browser = "firefox" + + -- If true, song notifications for Jamendo tracks and local tracks + -- will also contain album cover image. + musicwidget.show_album_cover = true + + -- Specify how big in pixels should an album cover be. Maximum value + -- is 100. + musicwidget.album_cover_size = 50 + + -- This option is necessary if you want the album covers to be shown + -- for your local tracks. + musicwidget.mpd_config = "/home/username/.mpdconf" + + -- Specify decorators on the left and the right side of the + -- widget. Or just leave empty strings if you decorate the widget + -- from outside. + musicwidget.ldecorator = " " + musicwidget.rdecorator = " " + + -- Set all the servers to work with (here can be any servers you use) + musicwidget.servers = { + { server = "localhost", + port = 6600 }, + { server = "192.168.0.72", + port = 6600 } + } + + -- Set the buttons of the widget. Keyboard keys are working in the + -- entire Awesome environment. Also look at the line 352. + musicwidget:register_buttons({ { "", awesompd.MOUSE_LEFT, musicwidget:command_playpause() }, + { "Control", awesompd.MOUSE_SCROLL_UP, musicwidget:command_prev_track() }, + { "Control", awesompd.MOUSE_SCROLL_DOWN, musicwidget:command_next_track() }, + { "", awesompd.MOUSE_SCROLL_UP, musicwidget:command_volume_up() }, + { "", awesompd.MOUSE_SCROLL_DOWN, musicwidget:command_volume_down() }, + { "", awesompd.MOUSE_RIGHT, musicwidget:command_show_menu() }, + { "", "XF86AudioLowerVolume", musicwidget:command_volume_down() }, + { "", "XF86AudioRaiseVolume", musicwidget:command_volume_up() }, + { modkey, "Pause", musicwidget:command_playpause() } }) + + musicwidget:run() -- After all configuration is done, run the widget + +-- END OF AWESOMPD WIDGET DECLARATION +-- Don't forget to add the widget to the wibox. It is done on the line 216. + +mywibox = {} +mypromptbox = {} +mylayoutbox = {} +mytaglist = {} +mytaglist.buttons = awful.util.table.join( + awful.button({ }, 1, awful.tag.viewonly), + awful.button({ modkey }, 1, awful.client.movetotag), + awful.button({ }, 3, awful.tag.viewtoggle), + awful.button({ modkey }, 3, awful.client.toggletag), + awful.button({ }, 4, awful.tag.viewnext), + awful.button({ }, 5, awful.tag.viewprev) + ) +mytasklist = {} +mytasklist.buttons = awful.util.table.join( + awful.button({ }, 1, function (c) + if not c:isvisible() then + awful.tag.viewonly(c:tags()[1]) + end + client.focus = c + c:raise() + end), + awful.button({ }, 3, function () + if instance then + instance:hide() + instance = nil + else + instance = awful.menu.clients({ width=250 }) + end + end), + awful.button({ }, 4, function () + awful.client.focus.byidx(1) + if client.focus then client.focus:raise() end + end), + awful.button({ }, 5, function () + awful.client.focus.byidx(-1) + if client.focus then client.focus:raise() end + end)) + +for s = 1, screen.count() do + -- Create a promptbox for each screen + mypromptbox[s] = awful.widget.prompt({ layout = awful.widget.layout.horizontal.leftright }) + -- Create an imagebox widget which will contains an icon indicating which layout we're using. + -- We need one layoutbox per screen. + mylayoutbox[s] = awful.widget.layoutbox(s) + mylayoutbox[s]:buttons(awful.util.table.join( + awful.button({ }, 1, function () awful.layout.inc(layouts, 1) end), + awful.button({ }, 3, function () awful.layout.inc(layouts, -1) end), + awful.button({ }, 4, function () awful.layout.inc(layouts, 1) end), + awful.button({ }, 5, function () awful.layout.inc(layouts, -1) end))) + -- Create a taglist widget + mytaglist[s] = awful.widget.taglist(s, awful.widget.taglist.label.all, mytaglist.buttons) + + -- Create a tasklist widget + mytasklist[s] = awful.widget.tasklist(function(c) + return awful.widget.tasklist.label.currenttags(c, s) + end, mytasklist.buttons) + + -- Create the wibox + mywibox[s] = awful.wibox({ position = "top", screen = s }) + -- Add widgets to the wibox - order matters + mywibox[s].widgets = { + { + mylauncher, + mytaglist[s], + mypromptbox[s], + layout = awful.widget.layout.horizontal.leftright + }, + mylayoutbox[s], + mytextclock, + musicwidget.widget, -- Awesompd widget is added like this + s == 1 and mysystray or nil, + mytasklist[s], + layout = awful.widget.layout.horizontal.rightleft + } +end +-- }}} + +-- {{{ Mouse bindings +root.buttons(awful.util.table.join( + awful.button({ }, 3, function () mymainmenu:toggle() end), + awful.button({ }, 4, awful.tag.viewnext), + awful.button({ }, 5, awful.tag.viewprev) +)) +-- }}} + +-- {{{ Key bindings +globalkeys = awful.util.table.join( + awful.key({ modkey, }, "Left", awful.tag.viewprev ), + awful.key({ modkey, }, "Right", awful.tag.viewnext ), + awful.key({ modkey, }, "Escape", awful.tag.history.restore), + + awful.key({ modkey, }, "j", + function () + awful.client.focus.byidx( 1) + if client.focus then client.focus:raise() end + end), + awful.key({ modkey, }, "k", + function () + awful.client.focus.byidx(-1) + if client.focus then client.focus:raise() end + end), + awful.key({ modkey, }, "w", function () mymainmenu:show({keygrabber=true}) end), + + -- Layout manipulation + awful.key({ modkey, "Shift" }, "j", function () awful.client.swap.byidx( 1) end), + awful.key({ modkey, "Shift" }, "k", function () awful.client.swap.byidx( -1) end), + awful.key({ modkey, "Control" }, "j", function () awful.screen.focus_relative( 1) end), + awful.key({ modkey, "Control" }, "k", function () awful.screen.focus_relative(-1) end), + awful.key({ modkey, }, "u", awful.client.urgent.jumpto), + awful.key({ modkey, }, "Tab", + function () + awful.client.focus.history.previous() + if client.focus then + client.focus:raise() + end + end), + + -- Standard program + awful.key({ modkey, }, "Return", function () awful.util.spawn(terminal) end), + awful.key({ modkey, "Control" }, "r", awesome.restart), + awful.key({ modkey, "Shift" }, "q", awesome.quit), + + awful.key({ modkey, }, "l", function () awful.tag.incmwfact( 0.05) end), + awful.key({ modkey, }, "h", function () awful.tag.incmwfact(-0.05) end), + awful.key({ modkey, "Shift" }, "h", function () awful.tag.incnmaster( 1) end), + awful.key({ modkey, "Shift" }, "l", function () awful.tag.incnmaster(-1) end), + awful.key({ modkey, "Control" }, "h", function () awful.tag.incncol( 1) end), + awful.key({ modkey, "Control" }, "l", function () awful.tag.incncol(-1) end), + awful.key({ modkey, }, "space", function () awful.layout.inc(layouts, 1) end), + awful.key({ modkey, "Shift" }, "space", function () awful.layout.inc(layouts, -1) end), + + -- Prompt + awful.key({ modkey }, "r", function () mypromptbox[mouse.screen]:run() end), + + awful.key({ modkey }, "x", + function () + awful.prompt.run({ prompt = "Run Lua code: " }, + mypromptbox[mouse.screen].widget, + awful.util.eval, nil, + awful.util.getdir("cache") .. "/history_eval") + end) +) + +clientkeys = awful.util.table.join( + awful.key({ modkey, }, "f", function (c) c.fullscreen = not c.fullscreen end), + awful.key({ modkey, "Shift" }, "c", function (c) c:kill() end), + awful.key({ modkey, "Control" }, "space", awful.client.floating.toggle ), + awful.key({ modkey, "Control" }, "Return", function (c) c:swap(awful.client.getmaster()) end), + awful.key({ modkey, }, "o", awful.client.movetoscreen ), + awful.key({ modkey, "Shift" }, "r", function (c) c:redraw() end), + awful.key({ modkey, }, "t", function (c) c.ontop = not c.ontop end), + awful.key({ modkey, }, "n", function (c) c.minimized = not c.minimized end), + awful.key({ modkey, }, "m", + function (c) + c.maximized_horizontal = not c.maximized_horizontal + c.maximized_vertical = not c.maximized_vertical + end) +) + +-- Compute the maximum number of digit we need, limited to 9 +keynumber = 0 +for s = 1, screen.count() do + keynumber = math.min(9, math.max(#tags[s], keynumber)); +end + +-- Bind all key numbers to tags. +-- Be careful: we use keycodes to make it works on any keyboard layout. +-- This should map on the top row of your keyboard, usually 1 to 9. +for i = 1, keynumber do + globalkeys = awful.util.table.join(globalkeys, + awful.key({ modkey }, "#" .. i + 9, + function () + local screen = mouse.screen + if tags[screen][i] then + awful.tag.viewonly(tags[screen][i]) + end + end), + awful.key({ modkey, "Control" }, "#" .. i + 9, + function () + local screen = mouse.screen + if tags[screen][i] then + awful.tag.viewtoggle(tags[screen][i]) + end + end), + awful.key({ modkey, "Shift" }, "#" .. i + 9, + function () + if client.focus and tags[client.focus.screen][i] then + awful.client.movetotag(tags[client.focus.screen][i]) + end + end), + awful.key({ modkey, "Control", "Shift" }, "#" .. i + 9, + function () + if client.focus and tags[client.focus.screen][i] then + awful.client.toggletag(tags[client.focus.screen][i]) + end + end)) +end + +clientbuttons = awful.util.table.join( + awful.button({ }, 1, function (c) client.focus = c; c:raise() end), + awful.button({ modkey }, 1, awful.mouse.client.move), + awful.button({ modkey }, 3, awful.mouse.client.resize)) + +-- Set keys +-- Add this line before root.keys(globalkeys). +musicwidget:append_global_keys() + +root.keys(globalkeys) +-- }}} + +-- {{{ Rules +awful.rules.rules = { + -- All clients will match this rule. + { rule = { }, + properties = { border_width = beautiful.border_width, + border_color = beautiful.border_normal, + focus = true, + keys = clientkeys, + buttons = clientbuttons } }, + { rule = { class = "MPlayer" }, + properties = { floating = true } }, + { rule = { class = "pinentry" }, + properties = { floating = true } }, + { rule = { class = "gimp" }, + properties = { floating = true } }, + -- Set Firefox to always map on tags number 2 of screen 1. + -- { rule = { class = "Firefox" }, + -- properties = { tag = tags[1][2] } }, +} +-- }}} + +-- {{{ Signals +-- Signal function to execute when a new client appears. +client.add_signal("manage", function (c, startup) + -- Add a titlebar + -- awful.titlebar.add(c, { modkey = modkey }) + + -- Enable sloppy focus + c:add_signal("mouse::enter", function(c) + if awful.layout.get(c.screen) ~= awful.layout.suit.magnifier + and awful.client.focus.filter(c) then + client.focus = c + end + end) + + if not startup then + -- Set the windows at the slave, + -- i.e. put it at the end of others instead of setting it master. + -- awful.client.setslave(c) + + -- Put windows in a smart way, only if they does not set an initial position. + if not c.size_hints.user_position and not c.size_hints.program_position then + awful.placement.no_overlap(c) + awful.placement.no_offscreen(c) + end + end +end) + +client.add_signal("focus", function(c) c.border_color = beautiful.border_focus end) +client.add_signal("unfocus", function(c) c.border_color = beautiful.border_normal end) +-- }}} diff --git a/awesompd/utf8.lua b/awesompd/utf8.lua new file mode 100644 index 0000000..72177c2 --- /dev/null +++ b/awesompd/utf8.lua @@ -0,0 +1,158 @@ +-- Provides UTF-8 aware string functions implemented in pure lua: +-- * string.utf8len(s) +-- * string.utf8sub(s, i, j) +-- +-- All functions behave as their non UTF-8 aware counterparts with the exception +-- that UTF-8 characters are used instead of bytes for all units. +-- +-- Note: all validations had been removed due to awesome usage specifics. +--[[ +Copyright (c) 2006-2007, Kyle Smith +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--]] + +-- ABNF from RFC 3629 +-- +-- UTF8-octets = *( UTF8-char ) +-- UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 +-- UTF8-1 = %x00-7F +-- UTF8-2 = %xC2-DF UTF8-tail +-- UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / +-- %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) +-- UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / +-- %xF4 %x80-8F 2( UTF8-tail ) +-- UTF8-tail = %x80-BF +-- + +-- returns the number of bytes used by the UTF-8 character at byte i in s +-- also doubles as a UTF-8 character validator +function utf8charbytes (s, i) + -- argument defaults + i = i or 1 + local c = string.byte(s, i) + + -- determine bytes needed for character, based on RFC 3629 + if c > 0 and c <= 127 then + -- UTF8-1 + return 1 + elseif c >= 194 and c <= 223 then + -- UTF8-2 + local c2 = string.byte(s, i + 1) + return 2 + elseif c >= 224 and c <= 239 then + -- UTF8-3 + local c2 = s:byte(i + 1) + local c3 = s:byte(i + 2) + return 3 + elseif c >= 240 and c <= 244 then + -- UTF8-4 + local c2 = s:byte(i + 1) + local c3 = s:byte(i + 2) + local c4 = s:byte(i + 3) + return 4 + end +end + +-- returns the number of characters in a UTF-8 string +function utf8len (s) + local pos = 1 + local bytes = string.len(s) + local len = 0 + + while pos <= bytes and len ~= chars do + local c = string.byte(s,pos) + len = len + 1 + + pos = pos + utf8charbytes(s, pos) + end + + if chars ~= nil then + return pos - 1 + end + + return len +end + +-- functions identically to string.sub except that i and j are UTF-8 characters +-- instead of bytes +function utf8sub (s, i, j) + j = j or -1 + + if i == nil then + return "" + end + + local pos = 1 + local bytes = string.len(s) + local len = 0 + + -- only set l if i or j is negative + local l = (i >= 0 and j >= 0) or utf8len(s) + local startChar = (i >= 0) and i or l + i + 1 + local endChar = (j >= 0) and j or l + j + 1 + + -- can't have start before end! + if startChar > endChar then + return "" + end + + -- byte offsets to pass to string.sub + local startByte, endByte = 1, bytes + + while pos <= bytes do + len = len + 1 + + if len == startChar then + startByte = pos + end + + pos = pos + utf8charbytes(s, pos) + + if len == endChar then + endByte = pos - 1 + break + end + end + + return string.sub(s, startByte, endByte) +end + +-- replace UTF-8 characters based on a mapping table +function utf8replace (s, mapping) + local pos = 1 + local bytes = string.len(s) + local charbytes + local newstr = "" + + while pos <= bytes do + charbytes = utf8charbytes(s, pos) + local c = string.sub(s, pos, pos + charbytes - 1) + newstr = newstr .. (mapping[c] or c) + pos = pos + charbytes + end + + return newstr +end diff --git a/bitcoin.lua b/bitcoin.lua new file mode 100644 index 0000000..8d29612 --- /dev/null +++ b/bitcoin.lua @@ -0,0 +1,50 @@ +-- {{{ init environment +local wakka = {} +local capi = { + mouse = mouse, + screen = screen +} + +-- {{{ display +-- formats the lines for the notify +local function display() + local lines = "Bitcoin:\n" + local tick = "MtGox:\n" + local f = io.popen("bitcoind getbalance", "r") + local t = io.popen("curl -q -s https://mtgox.com/code/data/ticker.php", "r") + local s = f:read('*all') + local g = t:read('*all') + line = lines .. "\n" .. s .. "\n" + ticker = tick .. "\n" .. g .. "\n" + f:close() + t:close() +-- return line, ticker + return string.format('%s%s',line, ticker) +end + +--local function ticker() +-- local ticker = "Ticker:\n" +-- local t = io.popen("curl -q -s https://mtgox.com/code/data/ticker.php", "r") +-- local q = t:read('*all') +-- line1 = ticker .. "\n" .. q .. "\n" +-- t:close() +-- return line1 +--end + +-- }}} +-- }}} + +function wakka.addToWidget(mywidget) + mywidget:add_signal('mouse::enter', function () + run_display = display() + usage = naughty.notify({ + text = string.format('%s', "monospace", run_display), + timeout = 0, + hover_timeout = 0.5, + screen = capi.mouse.screen + }) + end) + mywidget:add_signal('mouse::leave', function () naughty.destroy(usage) end) +end + +return wakka diff --git a/diskusage.lua b/diskusage.lua new file mode 100644 index 0000000..9bcfc1b --- /dev/null +++ b/diskusage.lua @@ -0,0 +1,133 @@ +-- @author Peter J. Kranz (Absurd-Mind, peter@myref.net) +-- Any questions, criticism or praise just drop me an email + +-- {{{ init environment +local M = {} +local capi = { + mouse = mouse, + screen = screen +} +units = {"KB", "MB", "GB", "TB", "PB", "EB"} +local usage = {} +-- }}} + +-- {{{ local functions +-- {{{ Unit formatter +-- formats a value to the corresponding unit +local function uformat(value) + local ret = tonumber(value) + for i, u in pairs(units) do + if ret < 1024 then + return string.format("%.1f" .. u, ret) + end + ret = ret / 1024; + end + return "N/A" +end +-- }}} + +-- {{{ getData +-- gets the required data from df +local function getData(onlyLocal) + -- Fallback to listing local filesystems + local warg = "" + if onlyLocal == true then + warg = "-l" + end + + local fs_info = {} -- Get data from df + local f = io.popen("LC_ALL=C df -kP " .. warg) + + for line in f:lines() do -- Match: (size) (used)(avail)(use%) (mount) + local s = string.match(line, "^.-[%s]([%d]+)") + local u,a,p = string.match(line, "([%d]+)[%D]+([%d]+)[%D]+([%d]+)%%") + local m = string.match(line, "%%[%s]([%p%w]+)") + + if u and m then -- Handle 1st line and broken regexp + fs_info[m] = {} + fs_info[m]["size"] = s + fs_info[m]["used"] = u + fs_info[m]["avail"] = a + fs_info[m]["used_p"] = tonumber(p) + fs_info[m]["avail_p"] = 100 - tonumber(p) + end + end + f:close() + return fs_info +end +-- }}} + +-- {{{ display +-- formats the lines for the notify +local function display(orange, red, onlyLocal) + data = getData(onlyLocal) + local lines = "diskusage:\n" + + local longest = 0 + local longestSize = 0; + local longestUsed = 0; + for i, m in pairs(data) do + if i:len() > longest then + longest = i:len() + end + + local s = uformat(m["size"]) + if s:len() > longestSize then + longestSize = s:len() + end + + local u = uformat(m["used"]) + if u:len() > longestUsed then + longestUsed = u:len() + end + end + longest = longest + 8 + + for i, m in pairs(data) do + local u = uformat(m["used"]) + local s = uformat(m["size"]) + + if m["used_p"] >= red then + lines = lines .. "" + elseif m["used_p"] >= orange then + lines = lines .. "" + else + lines = lines .. "" + end + + lines = lines + .. "\n" + .. i + .. string.rep(" ", longest + longestSize - i:len() - u:len()) + .. u + .. " / " + .. s + .. string.rep(" ", longestUsed - s:len()) + .. " (" + .. m["used_p"] + .. "%)" + end + + return lines +end +-- }}} +-- }}} + +-- {{{ global functions +function M.addToWidget(mywidget, orange, red, onlyLocal) + + mywidget:add_signal('mouse::enter', function () + + usage = naughty.notify({ + text = string.format('%s', "monospace", display(orange, red, onlyLocal)), + timeout = 0, + hover_timeout = 0.5, + screen = capi.mouse.screen + }) + + end) + mywidget:add_signal('mouse::leave', function () naughty.destroy(usage) end) +end +-- }}} + +return M \ No newline at end of file diff --git a/freedesktop/desktop.lua b/freedesktop/desktop.lua new file mode 100644 index 0000000..2188da3 --- /dev/null +++ b/freedesktop/desktop.lua @@ -0,0 +1,126 @@ +local wibox = wibox +local widget = widget +local screen = screen +local image = image +local button = button +local table = table +local ipairs = ipairs +local awful = require("awful") +local utils = require("freedesktop.utils") + +module("freedesktop.desktop") + +local current_pos = {} +local iconsize = { width = 48, height = 48 } +local labelsize = { width = 130, height = 20 } +local margin = { x = 20, y = 20 } + +function add_icon(settings) + + local s = settings.screen + + if not current_pos[s] then + current_pos[s] = { x = (screen[s].geometry.width - iconsize.width - margin.x), y = 40 } + end + + local totheight = (settings.icon and iconsize.height or 0) + (settings.label and labelsize.height or 0) + if totheight == 0 then return end + + if current_pos[s].y + totheight > screen[s].geometry.height - 40 then + current_pos[s].x = current_pos[s].x - labelsize.width - iconsize.width - margin.x + current_pos[s].y = 40 + end + + if (settings.icon) then + icon = awful.widget.button({ image = settings.icon }) + local newbuttons = icon:buttons() + table.insert(newbuttons, button({}, 1, nil, settings.click)); + icon:buttons(newbuttons) + + icon_container = wibox({ position = "floating", screen = s, bg = "#00000000" }) + icon_container.widgets = { icon } + icon_container:geometry({ + width = iconsize.width, + height = iconsize.height, + y = current_pos[s].y, + x = current_pos[s].x + }) + icon_container.screen = s + + current_pos[s].y = current_pos[s].y + iconsize.height + 5 + end + + if (settings.label) then + caption = widget({ type="textbox", align="right", width=labelsize.width }) + caption.ellipsize = "middle" + caption.text = settings.label + caption:buttons({ + button({ }, 1, settings.click) + }) + + caption_container = wibox({ position = "floating", screen = s, bg = "#00000000" }) + caption_container.widgets = { caption } + caption_container:geometry({ + width = labelsize.width, + height = labelsize.height, + y = current_pos[s].y, + x = current_pos[s].x - labelsize.width + iconsize.width + }) + caption_container.screen = s + end + + current_pos[s].y = current_pos[s].y + labelsize.height + margin.y +end + +--- Adds subdirs and files icons to the desktop +-- @param dir The directory to parse, (default is ~/Desktop) +-- @param showlabels Shows icon captions (default is false) +function add_applications_icons(arg) + for i, program in ipairs(utils.parse_desktop_files({ + dir = arg.dir or '~/Desktop/', + icon_sizes = { + iconsize.width .. "x" .. iconsize.height, + "128x128", "96x96", "72x72", "64x64", "48x48", + "36x36", "32x32", "24x24", "22x22", "16x6" + } + })) do + if program.show then + add_icon({ + label = arg.showlabels and program.Name or nil, + icon = program.icon_path, + screen = arg.screen, + click = function () awful.util.spawn(program.cmdline) end + }) + end + end +end + +--- Adds subdirs and files icons to the desktop +-- @param dir The directory to parse +-- @param showlabels Shows icon captions +-- @param open_with The program to use to open clicked files and dirs (i.e. xdg_open, thunar, etc.) +function add_dirs_and_files_icons(arg) + arg.open_with = arg.open_width or 'thunar' + for i, file in ipairs(utils.parse_dirs_and_files({ + dir = arg.dir or '~/Desktop/', + icon_sizes = { + iconsize.width .. "x" .. iconsize.height, + "128x128", "96x96", "72x72", "64x64", "48x48", + "36x36", "32x32", "24x24", "22x22", "16x6" + } + })) do + if file.show then + add_icon({ + label = arg.showlabels and file.filename or nil, + icon = file.icon, + screen = arg.screen, + click = function () awful.util.spawn(arg.open_with .. ' ' .. file.path) end + }) + end + end +end + +function add_desktop_icons(args) + add_applications_icons(args) + add_dirs_and_files_icons(args) +end diff --git a/freedesktop/menu.lua b/freedesktop/menu.lua new file mode 100644 index 0000000..a465661 --- /dev/null +++ b/freedesktop/menu.lua @@ -0,0 +1,97 @@ +-- Grab environment +local utils = require("freedesktop.utils") +local io = io +local string = string +local table = table +local os = os +local ipairs = ipairs +local pairs = pairs + +module("freedesktop.menu") + +all_menu_dirs = { + '/usr/share/applications/', + '/usr/local/share/applications/', + '~/.local/share/applications/' +} + +show_generic_name = false + +--- Create menus for applications +-- @param menu_dirs A list of application directories (optional). +-- @return A prepared menu w/ categories +function new(arg) + -- the categories and their synonyms where shamelessly copied from lxpanel + -- source code. + local programs = {} + local config = arg or {} + + programs['AudioVideo'] = {} + programs['Development'] = {} + programs['Education'] = {} + programs['Game'] = {} + programs['Graphics'] = {} + programs['Network'] = {} + programs['Office'] = {} + programs['Settings'] = {} + programs['System'] = {} + programs['Utility'] = {} + programs['Other'] = {} + + for i, dir in ipairs(config.menu_dirs or all_menu_dirs) do + local entries = utils.parse_desktop_files({dir = dir}) + for j, program in ipairs(entries) do + -- check whether to include in the menu + if program.show and program.Name and program.cmdline then + if show_generic_name and program.GenericName then + program.Name = program.Name .. ' (' .. program.GenericName .. ')' + end + local target_category = nil + if program.categories then + for _, category in ipairs(program.categories) do + if programs[category] then + target_category = category + break + end + end + end + if not target_category then + target_category = 'Other' + end + if target_category then + table.insert(programs[target_category], { program.Name, program.cmdline, program.icon_path }) + end + end + end + end + + -- sort each submenu alphabetically case insensitive + for k, v in pairs(programs) do + table.sort(v, function(a, b) return a[1]:lower() < b[1]:lower() end) + end + + local menu = { + { "Accessories", programs["Utility"], utils.lookup_icon({ icon = 'applications-accessories.png' }) }, + { "Development", programs["Development"], utils.lookup_icon({ icon = 'applications-development.png' }) }, + { "Education", programs["Education"], utils.lookup_icon({ icon = 'applications-science.png' }) }, + { "Games", programs["Game"], utils.lookup_icon({ icon = 'applications-games.png' }) }, + { "Graphics", programs["Graphics"], utils.lookup_icon({ icon = 'applications-graphics.png' }) }, + { "Internet", programs["Network"], utils.lookup_icon({ icon = 'applications-internet.png' }) }, + { "Multimedia", programs["AudioVideo"], utils.lookup_icon({ icon = 'applications-multimedia.png' }) }, + { "Office", programs["Office"], utils.lookup_icon({ icon = 'applications-office.png' }) }, + { "Other", programs["Other"], utils.lookup_icon({ icon = 'applications-other.png' }) }, + { "Settings", programs["Settings"], utils.lookup_icon({ icon = 'preferences-desktop.png' }) }, + { "System Tools", programs["System"], utils.lookup_icon({ icon = 'applications-system.png' }) }, + } + + -- Removing empty entries from menu + local cleanedMenu = {} + for index, item in ipairs(menu) do + itemTester = item[2] + if itemTester[1] then + table.insert(cleanedMenu, item) + end + end + + return cleanedMenu +end diff --git a/freedesktop/utils.lua b/freedesktop/utils.lua new file mode 100644 index 0000000..c5247c3 --- /dev/null +++ b/freedesktop/utils.lua @@ -0,0 +1,255 @@ +-- Grab environment + +local io = io +local os = os +local table = table +local type = type +local ipairs = ipairs +local pairs = pairs + +module("freedesktop.utils") + +terminal = 'xterm' + +icon_theme = nil + +all_icon_sizes = { + '128x128', + '96x96', + '72x72', + '64x64', + '48x48', + '36x36', + '32x32', + '24x24', + '22x22', + '16x16' +} +all_icon_types = { + 'apps', + 'actions', + 'devices', + 'places', + 'categories', + 'status', + 'mimetypes' +} +all_icon_paths = { os.getenv("HOME") .. '/.icons/', '/usr/share/icons/' } + +icon_sizes = {} + +local mime_types = {} + +function get_lines(...) + local f = io.popen(...) + return function () -- iterator + local data = f:read() + if data == nil then f:close() end + return data + end +end + +function file_exists(filename) + local file = io.open(filename, 'r') + local result = (file ~= nil) + if result then + file:close() + end + return result +end + +function lookup_icon(arg) + if arg.icon:sub(1, 1) == '/' and (arg.icon:find('.+%.png') or arg.icon:find('.+%.xpm')) then + -- icons with absolute path and supported (AFAICT) formats + return arg.icon + else + local icon_path = {} + local icon_themes = {} + local icon_theme_paths = {} + if icon_theme and type(icon_theme) == 'table' then + icon_themes = icon_theme + elseif icon_theme then + icon_themes = { icon_theme } + end + for i, theme in ipairs(icon_themes) do + for j, path in ipairs(all_icon_paths) do + table.insert(icon_theme_paths, path .. theme .. '/') + end + -- TODO also look in parent icon themes, as in freedesktop.org specification + end + table.insert(icon_theme_paths, '/usr/share/icons/hicolor/') -- fallback theme cf spec + + local isizes = icon_sizes + for i, sz in ipairs(all_icon_sizes) do + table.insert(isizes, sz) + end + + for i, icon_theme_directory in ipairs(icon_theme_paths) do + for j, size in ipairs(arg.icon_sizes or isizes) do + for k, icon_type in ipairs(all_icon_types) do + table.insert(icon_path, icon_theme_directory .. size .. '/' .. icon_type .. '/') + end + end + end + -- lowest priority fallbacks + table.insert(icon_path, '/usr/share/pixmaps/') + table.insert(icon_path, '/usr/share/icons/') + table.insert(icon_path, '/usr/share/app-install/icons/') + + for i, directory in ipairs(icon_path) do + if (arg.icon:find('.+%.png') or arg.icon:find('.+%.xpm')) and file_exists(directory .. arg.icon) then + return directory .. arg.icon + elseif file_exists(directory .. arg.icon .. '.png') then + return directory .. arg.icon .. '.png' + elseif file_exists(directory .. arg.icon .. '.xpm') then + return directory .. arg.icon .. '.xpm' + end + end + end +end + +function lookup_file_icon(arg) + load_mime_types() + + local extension = arg.filename:match('%a+$') + local mime = mime_types[extension] or '' + local mime_family = mime:match('^%a+') or '' + + -- possible icons in a typical gnome theme (i.e. Tango icons) + local possible_filenames = { + mime, + 'gnome-mime-' .. mime, + mime_family, + 'gnome-mime-' .. mime_family, + extension + } + + for i, filename in ipairs(possible_filenames) do + local icon = lookup_icon({icon = filename, icon_sizes = (arg.icon_sizes or all_icon_sizes)}) + if icon then + return icon + end + end + + -- If we don't find ad icon, then pretend is a plain text file + return lookup_icon({ icon = 'txt', icon_sizes = arg.icon_sizes or all_icon_sizes }) +end + +--- Load system MIME types +-- @return A table with file extension <--> MIME type mapping +function load_mime_types() + if #mime_types == 0 then + for line in io.lines('/etc/mime.types') do + if not line:find('^#') then + local parsed = {} + for w in line:gmatch('[^%s]+') do + table.insert(parsed, w) + end + if #parsed > 1 then + for i = 2, #parsed do + mime_types[parsed[i]] = parsed[1]:gsub('/', '-') + end + end + end + end + end +end + +--- Parse a .desktop file +-- @param file The .desktop file +-- @param requested_icon_sizes A list of icon sizes (optional). If this list is given, it will be used as a priority list for icon sizes when looking up for icons. If you want large icons, for example, you can put '128x128' as the first item in the list. +-- @return A table with file entries. +function parse_desktop_file(arg) + local program = { show = true, file = arg.file } + for line in io.lines(arg.file) do + for key, value in line:gmatch("(%w+)=(.+)") do + program[key] = value + end + end + + -- Don't show the program if NoDisplay is true + -- Only show the program if there is not OnlyShowIn attribute + -- or if it's equal to 'awesome' + if program.NoDisplay == "true" or program.OnlyShowIn ~= nil and program.OnlyShowIn ~= "awesome" then + program.show = false + end + + -- Look up for a icon. + if program.Icon then + program.icon_path = lookup_icon({ icon = program.Icon, icon_sizes = (arg.icon_sizes or all_icon_sizes) }) + if program.icon_path ~= nil and not file_exists(program.icon_path) then + program.icon_path = nil + end + end + + -- Split categories into a table. + if program.Categories then + program.categories = {} + for category in program.Categories:gmatch('[^;]+') do + table.insert(program.categories, category) + end + end + + if program.Exec then + local cmdline = program.Exec:gsub('%%c', program.Name) + cmdline = cmdline:gsub('%%[fmuFMU]', '') + cmdline = cmdline:gsub('%%k', program.file) + if program.icon_path then + cmdline = cmdline:gsub('%%i', '--icon ' .. program.icon_path) + else + cmdline = cmdline:gsub('%%i', '') + end + if program.Terminal == "true" then + cmdline = terminal .. ' -e ' .. cmdline + end + program.cmdline = cmdline + end + + return program +end + +--- Parse a directory with .desktop files +-- @param dir The directory. +-- @param icons_size, The icons sizes, optional. +-- @return A table with all .desktop entries. +function parse_desktop_files(arg) + local programs = {} + local files = get_lines('find '.. arg.dir ..' -name "*.desktop" 2>/dev/null') + for file in files do + arg.file = file + table.insert(programs, parse_desktop_file(arg)) + end + return programs +end + +--- Parse a directory files and subdirs +-- @param dir The directory. +-- @param icons_size, The icons sizes, optional. +-- @return A table with all .desktop entries. +function parse_dirs_and_files(arg) + local files = {} + local paths = get_lines('find '..arg.dir..' -maxdepth 1 -type d') + for path in paths do + if path:match("[^/]+$") then + local file = {} + file.filename = path:match("[^/]+$") + file.path = path + file.show = true + file.icon = lookup_icon({ icon = "folder", icon_sizes = (arg.icon_sizes or all_icon_sizes) }) + table.insert(files, file) + end + end + local paths = get_lines('find '..arg.dir..' -maxdepth 1 -type f') + for path in paths do + if not path:find("%.desktop$") then + local file = {} + file.filename = path:match("[^/]+$") + file.path = path + file.show = true + file.icon = lookup_file_icon({ filename = file.filename, icon_sizes = (arg.icon_sizes or all_icon_sizes) }) + table.insert(files, file) + end + end + return files +end + diff --git a/gfxtemp.lua b/gfxtemp.lua new file mode 100644 index 0000000..3dba7c7 --- /dev/null +++ b/gfxtemp.lua @@ -0,0 +1,33 @@ +-- {{{ init environment +local wakka = {} +local capi = { + mouse = mouse, + screen = screen +} + +-- {{{ display +-- formats the lines for the notify +local function display() + local lines = "GFX Temp:\n" + local f = io.popen("ssh setkeh@192.168.1.8 /opt/bin/aticonfig --odgt | grep Temperature | cut -c 43-52", "r") + local s = f:read('*all') + line = lines .. "\n" .. s .. "\n" + f:close() + return line +end +-- }}} +-- }}} + +function wakka.addToWidget(mywidget) + mywidget:add_signal('mouse::enter', function () + usage = naughty.notify({ + text = string.format('%s', "monospace", display()), + timeout = 0, + hover_timeout = 0.5, + screen = capi.mouse.screen + }) + end) + mywidget:add_signal('mouse::leave', function () naughty.destroy(usage) end) +end + +return wakka diff --git a/gmail_parser.py b/gmail_parser.py new file mode 100644 index 0000000..4000a5b --- /dev/null +++ b/gmail_parser.py @@ -0,0 +1,47 @@ +## check-gmail.py -- A command line util to check GMail -*- Python -*- +## modified to display mailbox summary for conky + +# ====================================================================== +# Copyright (C) 2006 Baishampayan Ghose +# Modified 2008 Hunter Loftis +# Time-stamp: Mon Jul 31, 2006 20:45+0530 +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# ====================================================================== + +import sys +import urllib # For BasicHTTPAuthentication +import feedparser # For parsing the feed +from textwrap import wrap + +_URL = "https://mail.google.com/gmail/feed/atom" + +uname = sys.argv[1] +password = sys.argv[2] +maxlen = sys.argv[3] + +urllib.FancyURLopener.prompt_user_passwd = lambda self, host, realm: (uname, password) + +def auth(): + '''The method to do HTTPBasicAuthentication''' + opener = urllib.FancyURLopener() + f = opener.open(_URL) + feed = f.read() + return feed + + +def readmail(feed, maxlen): + '''Parse the Atom feed and print a summary''' + atom = feedparser.parse(feed) + print '${color1} %s new email(s)\n' % (len(atom.entries)) + for i in range(min(len(atom.entries), maxlen)): + print ' ${color2}%s' % atom.entries[i].title +#uncomment the following line if you want to show the name of the sender +# print ' ${color2}%s' % atom.entries[i].author + if len(atom.entries) > maxlen: + print ' ${color}more...' + +if __name__ == "__main__": + f = auth() # Do auth and then get the feed + readmail(f, int(maxlen)) # Let the feed be chewed by feedparser diff --git a/html.lua b/html.lua new file mode 100644 index 0000000..3ed9469 --- /dev/null +++ b/html.lua @@ -0,0 +1,95 @@ +function HTML_ToText (text) + -- Declare variables, load the file. Make tags lowercase. + text = string.gsub (text,"(%b<>)", + function (tag) + return tag:lower() + end) + --[[ + First we kill the developer formatting (tabs, CR, LF) + and produce a long string with no newlines and tabs. + We also kill repeated spaces as browsers ignore them anyway. + ]] + local devkill= + { + ["("..string.char(10)..")"] = " ", + ["("..string.char(13)..")"] = " ", + ["("..string.char(15)..")"] = "", + ["(%s%s+)"]=" ", + } + for pat, res in pairs (devkill) do + text = string.gsub (text, pat, res) + end + -- Then we remove the header. We do this by stripping it first. + text = string.gsub (text, "(<%s*head[^>]*>)", "") + text = string.gsub (text, "(<%s*%/%s*head%s*>)", "") + text = string.gsub (text, "(,*<%/head>)", "") + -- Kill all scripts. First we nuke their attribs. + text = string.gsub (text, "(<%s*script[^>]*>)", "") + text = string.gsub (text, "(