├── .gitignore ├── Microsoft.VC90.CRT ├── Microsoft.VC90.CRT.manifest ├── msvcm90.dll ├── msvcp90.dll └── msvcr90.dll ├── README ├── build.bat ├── clean.bat ├── controller.py ├── controls.py ├── defaults.py ├── dummy.py ├── feeds.py ├── filters.py ├── icons ├── 16.png ├── 24.png ├── 256.png ├── 32.png ├── 48.png ├── about.png ├── accept.png ├── add.png ├── checked.png ├── cog.png ├── cog32.png ├── comment32.png ├── control_end.png ├── control_end_blue.png ├── control_fastforward.png ├── control_fastforward_blue.png ├── control_pause.png ├── control_pause_blue.png ├── control_play.png ├── control_play_blue.png ├── control_rewind.png ├── control_rewind_blue.png ├── control_start.png ├── control_start_blue.png ├── cross.png ├── cross_hover.png ├── delete.png ├── door_out.png ├── feed.ico ├── feed.png ├── feed.svg ├── feed32.png ├── feed_add.png ├── feed_delete.png ├── feed_disabled.png ├── feed_go.png ├── filter32.png ├── info32.png ├── information.png ├── transmit.png └── unchecked.png ├── idle.py ├── install.bat ├── installer.iss ├── ipc.py ├── license.txt ├── main.py ├── parsetab.py ├── patch ├── build_exe.py └── mf.py ├── popups.py ├── safe_pickle.py ├── settings.py ├── setup.py ├── sounds └── notification.wav ├── theme_default.py ├── themes ├── default │ ├── base.html │ ├── control_end.png │ ├── control_end_blue.png │ ├── control_fastforward.png │ ├── control_fastforward_blue.png │ ├── control_pause.png │ ├── control_pause_blue.png │ ├── control_play.png │ ├── control_play_blue.png │ ├── control_rewind.png │ ├── control_rewind_blue.png │ ├── control_start.png │ ├── control_start_blue.png │ ├── cross.png │ ├── cross_hover.png │ ├── feed.png │ ├── index.html │ └── style.css ├── minimal │ ├── base.html │ ├── cross.png │ ├── index.html │ └── style.css └── simons_quest │ ├── arrow_down.gif │ ├── base.html │ ├── bottom_left.png │ ├── bottom_right.png │ ├── horizontal.png │ ├── index.html │ ├── pixelette.eot │ ├── pixelette.ttf │ ├── style.css │ ├── top_left.png │ ├── top_right.png │ └── vertical.png ├── updater.py ├── util.py ├── view.py └── www ├── download.html ├── favicon.ico ├── firefox.html ├── images ├── contact.png ├── craigslist.png ├── edit_feed.png ├── feeds.png ├── firefox1.png ├── firefox2.png ├── firefox3.png ├── firefox4.png ├── firefox5.png ├── firefox6.png ├── icon.png ├── lifehacker.png ├── linux-desktop.png ├── linux-feeds.png ├── linux-options.png ├── linux-popups.png ├── logo.png ├── options.png ├── popup.png ├── popups.png ├── sidebar.png ├── sites │ ├── bbc.png │ ├── blogspot.png │ ├── cnet.png │ ├── cnn.png │ ├── craigslist.png │ ├── digg.png │ ├── ebay.png │ ├── engadget.png │ ├── espn.png │ ├── filehippo.png │ ├── gmail.png │ ├── google-blogs.png │ ├── google-finance.png │ ├── google-groups.png │ ├── google-news.png │ ├── linkedin.png │ ├── nytimes.png │ ├── reuters.png │ ├── slashdot.png │ ├── stackoverflow.png │ ├── twitter.png │ ├── wikipedia.png │ ├── wired.png │ ├── wordpress.png │ ├── xkcd.png │ ├── yelp.png │ └── youtube.png ├── superuser.png ├── themes │ ├── default.png │ ├── minimal.png │ └── simons_quest.png ├── weather.png └── yelp.png ├── index.html ├── pad.xml ├── popup ├── control_end.png ├── control_fastforward.png ├── control_pause.png ├── control_play.png ├── control_rewind.png ├── control_start.png ├── cross.png ├── feed.png └── index.html ├── screenshots.html ├── style.css ├── support.html ├── themes.html └── welcome.xml /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | installer 4 | feeds.dat 5 | log.txt 6 | settings.dat 7 | -------------------------------------------------------------------------------- /Microsoft.VC90.CRT/Microsoft.VC90.CRT.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | VF5ECUAHPV7EnUf+/UIXMPizPvs= 3Wg+StVMq2uhx7POnAkl2w4dDmY= /YfRn7UQENzdMeoMHxTgdRMiObA= 6 | -------------------------------------------------------------------------------- /Microsoft.VC90.CRT/msvcm90.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/Microsoft.VC90.CRT/msvcm90.dll -------------------------------------------------------------------------------- /Microsoft.VC90.CRT/msvcp90.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/Microsoft.VC90.CRT/msvcp90.dll -------------------------------------------------------------------------------- /Microsoft.VC90.CRT/msvcr90.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/Microsoft.VC90.CRT/msvcr90.dll -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/README -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | python setup.py 2 | "C:\Program Files (x86)\Inno Setup 5\Compil32.exe" /cc installer.iss 3 | copy dist\revision.txt installer 4 | -------------------------------------------------------------------------------- /clean.bat: -------------------------------------------------------------------------------- 1 | rmdir /S /Q build 2 | rmdir /S /Q dist 3 | rmdir /S /Q installer 4 | -------------------------------------------------------------------------------- /controller.py: -------------------------------------------------------------------------------- 1 | import wx 2 | import idle 3 | import feeds 4 | import popups 5 | import view 6 | import updater 7 | import util 8 | import winsound 9 | import socket 10 | from settings import settings 11 | 12 | class Controller(object): 13 | def __init__(self): 14 | socket.setdefaulttimeout(settings.SOCKET_TIMEOUT) 15 | self.icon = view.TaskBarIcon(self) 16 | self.manager = feeds.FeedManager() 17 | self.manager.load() 18 | self.add_default_feeds() 19 | self.popup = None 20 | self.polling = False 21 | self.enabled = True 22 | self.on_poll() 23 | self.on_check_for_updates() 24 | def add_default_feeds(self): 25 | if self.manager.feeds: 26 | return 27 | for url in settings.DEFAULT_FEED_URLS: 28 | feed = feeds.Feed(url) 29 | feed.interval = 60 * 60 * 24 30 | self.manager.add_feed(feed) 31 | def parse_args(self, message): 32 | urls = message.split('\n') 33 | for url in urls: 34 | url = url.strip() 35 | if not url: 36 | continue 37 | self.add_feed(url) 38 | def enable(self): 39 | self.icon.set_icon('icons/feed.png') 40 | self.enabled = True 41 | self.poll() 42 | def disable(self): 43 | self.icon.set_icon('icons/feed_disabled.png') 44 | self.enabled = False 45 | def save(self): 46 | self.manager.save() 47 | def on_check_for_updates(self): 48 | try: 49 | self.check_for_updates(False) 50 | finally: 51 | wx.CallLater(1000 * 60 * 5, self.on_check_for_updates) 52 | def check_for_updates(self, force=True): 53 | updater.run(self, force) 54 | def on_poll(self): 55 | try: 56 | self.poll() 57 | finally: 58 | wx.CallLater(1000 * 5, self.on_poll) 59 | def poll(self): 60 | if self.polling: 61 | return 62 | if not self.enabled: 63 | return 64 | if settings.DISABLE_WHEN_IDLE and idle.get_idle_duration() > settings.USER_IDLE_TIMEOUT: 65 | return 66 | if not self.manager.should_poll(): 67 | return 68 | self.polling = True 69 | self.icon.set_icon('icons/feed_go.png') 70 | util.start_thread(self._poll_thread) 71 | def _poll_thread(self): 72 | found_new = False 73 | try: 74 | for new_items in self.manager.poll(): 75 | found_new = True 76 | wx.CallAfter(self._poll_result, new_items) 77 | finally: 78 | wx.CallAfter(self._poll_complete, found_new) 79 | def _poll_result(self, new_items): 80 | items = self.manager.items 81 | if self.popup: 82 | index = self.popup.index 83 | else: 84 | index = len(items) 85 | items.extend(new_items) 86 | self.show_items(items, index, False) 87 | def _poll_complete(self, found_new): 88 | if found_new: 89 | self.save() 90 | self.polling = False 91 | self.icon.set_icon('icons/feed.png') 92 | def force_poll(self): 93 | for feed in self.manager.feeds: 94 | feed.last_poll = 0 95 | self.poll() 96 | def show_items(self, items, index, focus): 97 | play_sound = False 98 | if not items: 99 | return 100 | if not self.popup: 101 | self.popup = popups.PopupManager() 102 | self.popup.Bind(popups.EVT_POPUP_CLOSE, self.on_popup_close) 103 | if not focus: 104 | play_sound = True 105 | self.popup.set_items(items, index, focus) 106 | if focus: 107 | self.popup.auto = False 108 | if play_sound: 109 | self.play_sound() 110 | def play_sound(self): 111 | if settings.PLAY_SOUND: 112 | path = settings.SOUND_PATH 113 | flags = winsound.SND_FILENAME | winsound.SND_ASYNC 114 | try: 115 | winsound.PlaySound(path, flags) 116 | except Exception: 117 | pass 118 | def show_popup(self): 119 | items = self.manager.items 120 | index = len(items) - 1 121 | self.show_items(items, index, True) 122 | def add_feed(self, url=''): 123 | feed = view.AddFeedDialog.show_wizard(None, url) 124 | if not feed: 125 | return 126 | self.manager.add_feed(feed) 127 | self.save() 128 | self.poll() 129 | def edit_settings(self): 130 | window = view.SettingsDialog(None, self) 131 | window.Center() 132 | window.ShowModal() 133 | window.Destroy() 134 | def close(self): 135 | try: 136 | if self.popup: 137 | self.popup.on_close() 138 | wx.CallAfter(self.icon.Destroy) 139 | finally: 140 | pass #wx.GetApp().ExitMainLoop() 141 | def on_popup_close(self, event): 142 | self.popup = None 143 | self.manager.purge_items(settings.ITEM_CACHE_AGE) 144 | -------------------------------------------------------------------------------- /controls.py: -------------------------------------------------------------------------------- 1 | import wx 2 | import wx.lib.wordwrap as wordwrap 3 | import util 4 | 5 | class Event(wx.PyEvent): 6 | def __init__(self, event_object, type): 7 | super(Event, self).__init__() 8 | self.SetEventType(type.typeId) 9 | self.SetEventObject(event_object) 10 | 11 | EVT_HYPERLINK = wx.PyEventBinder(wx.NewEventType()) 12 | 13 | class Line(wx.PyPanel): 14 | def __init__(self, parent, pen=wx.BLACK_PEN): 15 | super(Line, self).__init__(parent, -1, style=wx.BORDER_NONE) 16 | self.pen = pen 17 | self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) 18 | self.Bind(wx.EVT_PAINT, self.on_paint) 19 | self.Bind(wx.EVT_SIZE, self.on_size) 20 | def on_size(self, event): 21 | self.Refresh() 22 | def on_paint(self, event): 23 | dc = wx.AutoBufferedPaintDC(self) 24 | dc.Clear() 25 | dc.SetPen(self.pen) 26 | width, height = self.GetClientSize() 27 | y = height / 2 28 | dc.DrawLine(0, y, width, y) 29 | def DoGetBestSize(self): 30 | return -1, self.pen.GetWidth() 31 | 32 | class Text(wx.PyPanel): 33 | def __init__(self, parent, width, text): 34 | super(Text, self).__init__(parent, -1, style=wx.BORDER_NONE) 35 | self.text = text 36 | self.width = width 37 | self.wrap = True 38 | self.rects = [] 39 | self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) 40 | self.Bind(wx.EVT_PAINT, self.on_paint) 41 | self.Bind(wx.EVT_SIZE, self.on_size) 42 | def on_size(self, event): 43 | self.Refresh() 44 | def on_paint(self, event): 45 | dc = wx.AutoBufferedPaintDC(self) 46 | self.setup_dc(dc) 47 | dc.Clear() 48 | self.draw_lines(dc) 49 | def setup_dc(self, dc): 50 | parent = self.GetParent() 51 | dc.SetFont(self.GetFont()) 52 | dc.SetTextBackground(parent.GetBackgroundColour()) 53 | dc.SetTextForeground(parent.GetForegroundColour()) 54 | dc.SetBackground(wx.Brush(parent.GetBackgroundColour())) 55 | def draw_lines(self, dc, emulate=False): 56 | if self.wrap: 57 | text = wordwrap.wordwrap(self.text.strip(), self.width, dc) 58 | else: 59 | text = self.text.strip() 60 | lines = text.split('\n') 61 | lines = [line.strip() for line in lines] 62 | lines = [line for line in lines if line] 63 | x, y = 0, 0 64 | rects = [] 65 | for line in lines: 66 | if not emulate: 67 | dc.DrawText(line, x, y) 68 | w, h = dc.GetTextExtent(line) 69 | rects.append(wx.Rect(x, y, w, h)) 70 | y += h 71 | if not emulate: 72 | self.rects = rects 73 | return y 74 | def compute_height(self): 75 | dc = wx.ClientDC(self) 76 | self.setup_dc(dc) 77 | height = self.draw_lines(dc, True) 78 | return height 79 | def fit_no_wrap(self): 80 | dc = wx.ClientDC(self) 81 | self.setup_dc(dc) 82 | width, height = dc.GetTextExtent(self.text.strip()) 83 | self.width = width 84 | self.wrap = False 85 | def DoGetBestSize(self): 86 | height = self.compute_height() 87 | return self.width, height 88 | 89 | class Link(Text): 90 | def __init__(self, parent, width, link, text): 91 | super(Link, self).__init__(parent, width, text) 92 | self.link = link 93 | self.trigger = False 94 | self.hover = False 95 | self.Bind(wx.EVT_LEAVE_WINDOW, self.on_leave) 96 | self.Bind(wx.EVT_MOTION, self.on_motion) 97 | self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down) 98 | self.Bind(wx.EVT_LEFT_UP, self.on_left_up) 99 | self.Bind(wx.EVT_RIGHT_UP, self.on_right_up) 100 | def hit_test(self, point): 101 | for rect in self.rects: 102 | if rect.Contains(point): 103 | self.on_hover() 104 | break 105 | else: 106 | self.on_unhover() 107 | def on_motion(self, event): 108 | self.hit_test(event.GetPosition()) 109 | def on_leave(self, event): 110 | self.on_unhover() 111 | def on_hover(self): 112 | if self.hover: 113 | return 114 | self.hover = True 115 | font = self.GetFont() 116 | font.SetUnderlined(True) 117 | self.SetFont(font) 118 | self.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) 119 | self.Refresh() 120 | def on_unhover(self): 121 | if not self.hover: 122 | return 123 | self.hover = False 124 | self.trigger = False 125 | font = self.GetFont() 126 | font.SetUnderlined(False) 127 | self.SetFont(font) 128 | self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) 129 | self.Refresh() 130 | def on_left_down(self, event): 131 | if self.hover: 132 | self.trigger = True 133 | def on_left_up(self, event): 134 | if self.hover and self.trigger: 135 | self.post_event() 136 | self.trigger = False 137 | def on_right_up(self, event): 138 | menu = wx.Menu() 139 | util.menu_item(menu, 'Open Link', self.on_open_link) 140 | util.menu_item(menu, 'Copy Link', self.on_copy_link) 141 | self.PopupMenu(menu, event.GetPosition()) 142 | def on_open_link(self, event): 143 | self.post_event() 144 | def on_copy_link(self, event): 145 | if wx.TheClipboard.Open(): 146 | wx.TheClipboard.SetData(wx.TextDataObject(self.link)) 147 | wx.TheClipboard.Close() 148 | def post_event(self): 149 | event = Event(self, EVT_HYPERLINK) 150 | event.link = self.link 151 | wx.PostEvent(self, event) 152 | 153 | class BitmapLink(wx.PyPanel): 154 | def __init__(self, parent, link, bitmap, hover_bitmap=None): 155 | super(BitmapLink, self).__init__(parent, -1) 156 | self.link = link 157 | self.bitmap = bitmap 158 | self.hover_bitmap = hover_bitmap or bitmap 159 | self.hover = False 160 | self.trigger = False 161 | self.SetInitialSize(bitmap.GetSize()) 162 | self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) 163 | self.Bind(wx.EVT_PAINT, self.on_paint) 164 | self.Bind(wx.EVT_ENTER_WINDOW, self.on_enter) 165 | self.Bind(wx.EVT_LEAVE_WINDOW, self.on_leave) 166 | self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down) 167 | self.Bind(wx.EVT_LEFT_UP, self.on_left_up) 168 | def on_paint(self, event): 169 | parent = self.GetParent() 170 | dc = wx.AutoBufferedPaintDC(self) 171 | dc.SetBackground(wx.Brush(parent.GetBackgroundColour())) 172 | dc.Clear() 173 | bitmap = self.hover_bitmap if self.hover else self.bitmap 174 | dc.DrawBitmap(bitmap, 0, 0, True) 175 | def on_enter(self, event): 176 | self.hover = True 177 | self.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) 178 | self.Refresh() 179 | def on_leave(self, event): 180 | self.trigger = False 181 | self.hover = False 182 | self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) 183 | self.Refresh() 184 | def on_left_down(self, event): 185 | self.trigger = True 186 | def on_left_up(self, event): 187 | if self.trigger: 188 | event = Event(self, EVT_HYPERLINK) 189 | event.link = self.link 190 | wx.PostEvent(self, event) 191 | self.trigger = False 192 | -------------------------------------------------------------------------------- /defaults.py: -------------------------------------------------------------------------------- 1 | # Helper Functions 2 | def load_revision(): 3 | try: 4 | with open('revision.txt', 'r') as file: 5 | return int(file.read().strip()) 6 | except Exception: 7 | return -1 8 | 9 | # Popup Settings 10 | POPUP_DURATION = 5 11 | POPUP_AUTO_PLAY = True 12 | POPUP_WAIT_ON_HOVER = True 13 | POPUP_THEME = 'default' 14 | POPUP_WIDTH = 400 15 | POPUP_POSITION = (1, 1) 16 | POPUP_TRANSPARENCY = 230 17 | POPUP_TITLE_LENGTH = 120 18 | POPUP_BODY_LENGTH = 400 19 | POPUP_DISPLAY = 0 20 | POPUP_STAY_ON_TOP = True 21 | POPUP_BORDER_SIZE = 3 22 | POPUP_BORDER_COLOR = (0, 0, 0) 23 | 24 | # Application Settings 25 | APP_ID = 'FeedNotifier' 26 | APP_NAME = 'Feed Notifier' 27 | APP_VERSION = '2.6' 28 | APP_URL = 'http://www.feednotifier.com/' 29 | USER_AGENT = '%s/%s +%s' % (APP_ID, APP_VERSION, APP_URL) 30 | DEFAULT_POLLING_INTERVAL = 60 * 15 31 | USER_IDLE_TIMEOUT = 60 32 | DISABLE_WHEN_IDLE = True 33 | ITEM_CACHE_AGE = 60 * 60 * 24 * 1 34 | FEED_CACHE_SIZE = 1000 35 | MAX_WORKER_THREADS = 10 36 | PLAY_SOUND = True 37 | SOUND_PATH = 'sounds/notification.wav' 38 | SOCKET_TIMEOUT = 15 39 | 40 | # Initial Setup 41 | DEFAULT_FEED_URLS = [ 42 | 'http://www.feednotifier.com/welcome.xml', 43 | ] 44 | 45 | # Proxy Settings 46 | USE_PROXY = False 47 | PROXY_URL = '' 48 | 49 | # Updater Settings 50 | LOCAL_REVISION = load_revision() 51 | REVISION_URL = 'http://www.feednotifier.com/update/revision.txt' 52 | INSTALLER_URL = 'http://www.feednotifier.com/update/installer.exe' 53 | CHECK_FOR_UPDATES = True 54 | UPDATE_INTERVAL = 60 * 60 * 24 * 1 55 | UPDATE_TIMESTAMP = 0 56 | 57 | del load_revision 58 | -------------------------------------------------------------------------------- /dummy.py: -------------------------------------------------------------------------------- 1 | # used for dummy.__file__ to setup path 2 | -------------------------------------------------------------------------------- /feeds.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import calendar 4 | import uuid 5 | import urlparse 6 | import urllib2 7 | import filters 8 | import util 9 | import Queue 10 | import logging 11 | import safe_pickle 12 | from settings import settings 13 | 14 | def cmp_timestamp(a, b): 15 | return cmp(a.timestamp, b.timestamp) 16 | 17 | def create_id(entry): 18 | keys = ['id', 'link', 'title'] 19 | values = tuple(util.get(entry, key, None) for key in keys) 20 | return values if any(values) else uuid.uuid4().hex 21 | 22 | class Item(object): 23 | def __init__(self, feed, id): 24 | self.feed = feed 25 | self.id = id 26 | self.timestamp = int(time.time()) 27 | self.received = int(time.time()) 28 | self.title = '' 29 | self.description = '' 30 | self.link = '' 31 | self.author = '' 32 | self.read = False 33 | @property 34 | def time_since(self): 35 | return util.time_since(self.timestamp) 36 | 37 | class Feed(object): 38 | def __init__(self, url): 39 | self.uuid = uuid.uuid4().hex 40 | self.url = url 41 | self.username = None 42 | self.password = None 43 | self.enabled = True 44 | self.last_poll = 0 45 | self.interval = settings.DEFAULT_POLLING_INTERVAL 46 | self.etag = None 47 | self.modified = None 48 | self.title = '' 49 | self.link = '' 50 | self.clicks = 0 51 | self.item_count = 0 52 | self.color = None 53 | self.id_list = [] 54 | self.id_set = set() 55 | def make_copy(self): 56 | feed = Feed(self.url) 57 | for key in ['uuid', 'enabled', 'interval', 'title', 'link', 'clicks', 'item_count', 'color']: 58 | value = getattr(self, key) 59 | setattr(feed, key, value) 60 | return feed 61 | def copy_from(self, feed): 62 | for key in ['enabled', 'interval', 'title', 'link', 'color']: 63 | value = getattr(feed, key) 64 | setattr(self, key, value) 65 | @property 66 | def favicon_url(self): 67 | components = urlparse.urlsplit(self.link) 68 | scheme, domain = components[:2] 69 | return '%s://%s/favicon.ico' % (scheme, domain) 70 | @property 71 | def favicon_path(self): 72 | components = urlparse.urlsplit(self.link) 73 | scheme, domain = components[:2] 74 | path = 'icons/cache/%s.ico' % domain 75 | return os.path.abspath(path) 76 | @property 77 | def has_favicon(self): 78 | return os.path.exists(self.favicon_path) 79 | def download_favicon(self): 80 | # make cache directory if needed 81 | try: 82 | dir, name = os.path.split(self.favicon_path) 83 | os.makedirs(dir) 84 | except Exception: 85 | pass 86 | # try to download the favicon 87 | try: 88 | opener = urllib2.build_opener(util.get_proxy()) 89 | f = opener.open(self.favicon_url) 90 | data = f.read() 91 | f.close() 92 | f = open(self.favicon_path, 'wb') 93 | f.write(data) 94 | f.close() 95 | except Exception: 96 | pass 97 | def clear_cache(self): 98 | self.id_list = [] 99 | self.id_set = set() 100 | self.etag = None 101 | self.modified = None 102 | def clean_cache(self, size): 103 | for id in self.id_list[:-size]: 104 | self.id_set.remove(id) 105 | self.id_list = self.id_list[-size:] 106 | def should_poll(self): 107 | if not self.enabled: 108 | return False 109 | now = int(time.time()) 110 | duration = now - self.last_poll 111 | return duration >= self.interval 112 | def poll(self, timestamp, filters): 113 | logging.info('Polling feed "%s"' % self.url) 114 | result = [] 115 | self.last_poll = timestamp 116 | username = util.decode_password(self.username) 117 | password = util.decode_password(self.password) 118 | d = util.parse(self.url, username, password, self.etag, self.modified) 119 | self.etag = util.get(d, 'etag', None) 120 | self.modified = util.get(d, 'modified', None) 121 | feed = util.get(d, 'feed', None) 122 | if feed: 123 | self.title = self.title or util.get(feed, 'title', '') 124 | self.link = self.link or util.get(feed, 'link', self.url) 125 | entries = util.get(d, 'entries', []) 126 | for entry in reversed(entries): 127 | id = create_id(entry) 128 | if id in self.id_set: 129 | continue 130 | self.item_count += 1 131 | self.id_list.append(id) 132 | self.id_set.add(id) 133 | item = Item(self, id) 134 | item.timestamp = calendar.timegm(util.get(entry, 'date_parsed', time.gmtime())) 135 | item.title = util.format(util.get(entry, 'title', ''), settings.POPUP_TITLE_LENGTH) 136 | item.description = util.format(util.get(entry, 'description', ''), settings.POPUP_BODY_LENGTH) 137 | item.link = util.get(entry, 'link', '') 138 | item.author = util.format(util.get(entry, 'author', '')) # TODO: max length 139 | if all(filter.filter(item) for filter in filters): 140 | result.append(item) 141 | self.clean_cache(settings.FEED_CACHE_SIZE) 142 | return result 143 | 144 | class Filter(object): 145 | def __init__(self, code, ignore_case=True, whole_word=True, feeds=None): 146 | self.uuid = uuid.uuid4().hex 147 | self.enabled = True 148 | self.code = code 149 | self.ignore_case = ignore_case 150 | self.whole_word = whole_word 151 | self.feeds = set(feeds) if feeds else set() 152 | self.inputs = 0 153 | self.outputs = 0 154 | def make_copy(self): 155 | filter = Filter(self.code, self.ignore_case, self.whole_word, self.feeds) 156 | for key in ['uuid', 'enabled', 'inputs', 'outputs']: 157 | value = getattr(self, key) 158 | setattr(filter, key, value) 159 | return filter 160 | def copy_from(self, filter): 161 | for key in ['enabled', 'code', 'ignore_case', 'whole_word', 'feeds']: 162 | value = getattr(filter, key) 163 | setattr(self, key, value) 164 | def filter(self, item): 165 | if not self.enabled: 166 | return True 167 | if self.feeds and item.feed not in self.feeds: 168 | return True 169 | self.inputs += 1 170 | rule = filters.parse(self.code) # TODO: cache parsed rules 171 | if rule.evaluate(item, self.ignore_case, self.whole_word): 172 | self.outputs += 1 173 | return True 174 | else: 175 | return False 176 | 177 | class FeedManager(object): 178 | def __init__(self): 179 | self.feeds = [] 180 | self.items = [] 181 | self.filters = [] 182 | def add_feed(self, feed): 183 | logging.info('Adding feed "%s"' % feed.url) 184 | self.feeds.append(feed) 185 | def remove_feed(self, feed): 186 | logging.info('Removing feed "%s"' % feed.url) 187 | self.feeds.remove(feed) 188 | for filter in self.filters: 189 | filter.feeds.discard(feed) 190 | def add_filter(self, filter): 191 | logging.info('Adding filter "%s"' % filter.code) 192 | self.filters.append(filter) 193 | def remove_filter(self, filter): 194 | logging.info('Removing filter "%s"' % filter.code) 195 | self.filters.remove(filter) 196 | def should_poll(self): 197 | return any(feed.should_poll() for feed in self.feeds) 198 | def poll(self): 199 | now = int(time.time()) 200 | jobs = Queue.Queue() 201 | results = Queue.Queue() 202 | feeds = [feed for feed in self.feeds if feed.should_poll()] 203 | for feed in feeds: 204 | jobs.put(feed) 205 | count = len(feeds) 206 | logging.info('Starting worker threads') 207 | for i in range(min(count, settings.MAX_WORKER_THREADS)): 208 | util.start_thread(self.worker, now, jobs, results) 209 | while count: 210 | items = results.get() 211 | count -= 1 212 | if items: 213 | yield items 214 | logging.info('Worker threads completed') 215 | def worker(self, now, jobs, results): 216 | while True: 217 | try: 218 | feed = jobs.get(False) 219 | except Queue.Empty: 220 | break 221 | try: 222 | items = feed.poll(now, self.filters) 223 | items.sort(cmp=cmp_timestamp) 224 | if items and not feed.has_favicon: 225 | feed.download_favicon() 226 | results.put(items) 227 | jobs.task_done() 228 | except Exception: 229 | results.put([]) 230 | jobs.task_done() 231 | def purge_items(self, max_age): 232 | now = int(time.time()) 233 | feeds = set(self.feeds) 234 | for item in list(self.items): 235 | age = now - item.received 236 | if age > max_age or item.feed not in feeds: 237 | self.items.remove(item) 238 | def load(self, path='feeds.dat'): 239 | logging.info('Loading feed data from "%s"' % path) 240 | try: 241 | data = safe_pickle.load(path) 242 | except Exception: 243 | data = ([], [], []) 244 | # backward compatibility 245 | if len(data) == 2: 246 | self.feeds, self.items = data 247 | self.filters = [] 248 | else: 249 | self.feeds, self.items, self.filters = data 250 | attributes = { 251 | 'clicks': 0, 252 | 'item_count': 0, 253 | 'username': None, 254 | 'password': None, 255 | 'color': None, 256 | } 257 | for feed in self.feeds: 258 | for name, value in attributes.iteritems(): 259 | if not hasattr(feed, name): 260 | setattr(feed, name, value) 261 | if not hasattr(feed, 'id_list'): 262 | feed.id_list = list(feed.id_set) 263 | logging.info('Loaded %d feeds, %d items, %d filters' % (len(self.feeds), len(self.items), len(self.filters))) 264 | def save(self, path='feeds.dat'): 265 | logging.info('Saving feed data to "%s"' % path) 266 | data = (self.feeds, self.items, self.filters) 267 | safe_pickle.save(path, data) 268 | def clear_item_history(self): 269 | logging.info('Clearing item history') 270 | del self.items[:] 271 | def clear_feed_cache(self): 272 | logging.info('Clearing feed caches') 273 | for feed in self.feeds: 274 | feed.clear_cache() 275 | -------------------------------------------------------------------------------- /filters.py: -------------------------------------------------------------------------------- 1 | # Keyword Filter Parser 2 | 3 | EXCLUDE = 0 4 | INCLUDE = 1 5 | 6 | ALL = 0xf 7 | TITLE = 1 8 | LINK = 2 9 | AUTHOR = 4 10 | CONTENT = 8 11 | 12 | TYPES = { 13 | None: INCLUDE, 14 | '+': INCLUDE, 15 | '-': EXCLUDE, 16 | } 17 | 18 | QUALIFIERS = { 19 | None: ALL, 20 | 'title:': TITLE, 21 | 'link:': LINK, 22 | 'author:': AUTHOR, 23 | 'content:': CONTENT, 24 | } 25 | 26 | TYPE_STR = { 27 | EXCLUDE: '-', 28 | INCLUDE: '+', 29 | } 30 | 31 | QUALIFIER_STR = { 32 | ALL: 'all', 33 | TITLE: 'title', 34 | LINK: 'link', 35 | AUTHOR: 'author', 36 | CONTENT: 'content', 37 | } 38 | 39 | class Rule(object): 40 | def __init__(self, type, qualifier, word): 41 | self.type = TYPES.get(type, type) 42 | self.qualifier = QUALIFIERS.get(qualifier, qualifier) 43 | self.word = word 44 | def evaluate(self, item, ignore_case=True, whole_word=True): 45 | strings = [] 46 | if self.qualifier & TITLE: 47 | strings.append(item.title) 48 | if self.qualifier & LINK: 49 | strings.append(item.link) 50 | if self.qualifier & AUTHOR: 51 | strings.append(item.author) 52 | if self.qualifier & CONTENT: 53 | strings.append(item.description) 54 | text = '\n'.join(strings) 55 | word = self.word 56 | if ignore_case: 57 | text = text.lower() 58 | word = word.lower() 59 | if whole_word: 60 | text = set(text.split()) 61 | if word in text: 62 | return self.type == INCLUDE 63 | else: 64 | return self.type == EXCLUDE 65 | def __str__(self): 66 | type = TYPE_STR[self.type] 67 | qualifier = QUALIFIER_STR[self.qualifier] 68 | return '(%s, %s, "%s")' % (type, qualifier, self.word) 69 | 70 | class AndRule(object): 71 | def __init__(self, left, right): 72 | self.left = left 73 | self.right = right 74 | def evaluate(self, item, ignore_case=True, whole_word=True): 75 | a = self.left.evaluate(item, ignore_case, whole_word) 76 | b = self.right.evaluate(item, ignore_case, whole_word) 77 | return a and b 78 | def __str__(self): 79 | return '(%s and %s)' % (self.left, self.right) 80 | 81 | class OrRule(object): 82 | def __init__(self, left, right): 83 | self.left = left 84 | self.right = right 85 | def evaluate(self, item, ignore_case=True, whole_word=True): 86 | a = self.left.evaluate(item, ignore_case, whole_word) 87 | b = self.right.evaluate(item, ignore_case, whole_word) 88 | return a or b 89 | def __str__(self): 90 | return '(%s or %s)' % (self.left, self.right) 91 | 92 | class NotRule(object): 93 | def __init__(self, rule): 94 | self.rule = rule 95 | def evaluate(self, item, ignore_case=True, whole_word=True): 96 | return not self.rule.evaluate(item, ignore_case, whole_word) 97 | def __str__(self): 98 | return '(not %s)' % (self.rule) 99 | 100 | # Lexer Rules 101 | reserved = { 102 | 'and': 'AND', 103 | 'or': 'OR', 104 | 'not': 'NOT', 105 | } 106 | 107 | tokens = [ 108 | 'PLUS', 109 | 'MINUS', 110 | 'LPAREN', 111 | 'RPAREN', 112 | 'TITLE', 113 | 'LINK', 114 | 'AUTHOR', 115 | 'CONTENT', 116 | 'WORD', 117 | ] + reserved.values() 118 | 119 | t_PLUS = r'\+' 120 | t_MINUS = r'\-' 121 | t_LPAREN = r'\(' 122 | t_RPAREN = r'\)' 123 | 124 | def t_TITLE(t): 125 | r'title:' 126 | return t 127 | 128 | def t_LINK(t): 129 | r'link:' 130 | return t 131 | 132 | def t_AUTHOR(t): 133 | r'author:' 134 | return t 135 | 136 | def t_CONTENT(t): 137 | r'content:' 138 | return t 139 | 140 | def t_WORD(t): 141 | r'(\'[^\']+\') | (\"[^\"]+\") | ([^ \n\t\r+\-()\'"]+)' 142 | t.type = reserved.get(t.value, 'WORD') 143 | if t.value[0] == '"' and t.value[-1] == '"': 144 | t.value = t.value[1:-1] 145 | if t.value[0] == "'" and t.value[-1] == "'": 146 | t.value = t.value[1:-1] 147 | return t 148 | 149 | t_ignore = ' \n\t\r' 150 | 151 | def t_error(t): 152 | raise Exception 153 | 154 | # Parser Rules 155 | precedence = ( 156 | ('left', 'OR'), 157 | ('left', 'AND'), 158 | ('right', 'NOT') 159 | ) 160 | 161 | def p_filter(t): 162 | 'filter : expression' 163 | t[0] = t[1] 164 | 165 | def p_expression_rule(t): 166 | 'expression : rule' 167 | t[0] = t[1] 168 | 169 | def p_expression_and(t): 170 | 'expression : expression AND expression' 171 | t[0] = AndRule(t[1], t[3]) 172 | 173 | def p_expression_or(t): 174 | 'expression : expression OR expression' 175 | t[0] = OrRule(t[1], t[3]) 176 | 177 | def p_expression_not(t): 178 | 'expression : NOT expression' 179 | t[0] = NotRule(t[2]) 180 | 181 | def p_expression_group(t): 182 | 'expression : LPAREN expression RPAREN' 183 | t[0] = t[2] 184 | 185 | def p_rule(t): 186 | 'rule : type qualifier WORD' 187 | t[0] = Rule(t[1], t[2], t[3]) 188 | 189 | def p_type(t): 190 | '''type : PLUS 191 | | MINUS 192 | | empty''' 193 | t[0] = t[1] 194 | 195 | def p_qualifier(t): 196 | '''qualifier : TITLE 197 | | LINK 198 | | AUTHOR 199 | | CONTENT 200 | | empty''' 201 | t[0] = t[1] 202 | 203 | def p_empty(t): 204 | 'empty :' 205 | pass 206 | 207 | def p_error(t): 208 | raise Exception 209 | 210 | import ply.lex as lex 211 | import ply.yacc as yacc 212 | 213 | lexer = lex.lex() 214 | parser = yacc.yacc() 215 | 216 | def parse(text): 217 | return parser.parse(text, lexer=lexer) 218 | 219 | if __name__ == '__main__': 220 | while True: 221 | text = raw_input('> ') 222 | print parse(text) 223 | -------------------------------------------------------------------------------- /icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/16.png -------------------------------------------------------------------------------- /icons/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/24.png -------------------------------------------------------------------------------- /icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/256.png -------------------------------------------------------------------------------- /icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/32.png -------------------------------------------------------------------------------- /icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/48.png -------------------------------------------------------------------------------- /icons/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/about.png -------------------------------------------------------------------------------- /icons/accept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/accept.png -------------------------------------------------------------------------------- /icons/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/add.png -------------------------------------------------------------------------------- /icons/checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/checked.png -------------------------------------------------------------------------------- /icons/cog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/cog.png -------------------------------------------------------------------------------- /icons/cog32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/cog32.png -------------------------------------------------------------------------------- /icons/comment32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/comment32.png -------------------------------------------------------------------------------- /icons/control_end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/control_end.png -------------------------------------------------------------------------------- /icons/control_end_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/control_end_blue.png -------------------------------------------------------------------------------- /icons/control_fastforward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/control_fastforward.png -------------------------------------------------------------------------------- /icons/control_fastforward_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/control_fastforward_blue.png -------------------------------------------------------------------------------- /icons/control_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/control_pause.png -------------------------------------------------------------------------------- /icons/control_pause_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/control_pause_blue.png -------------------------------------------------------------------------------- /icons/control_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/control_play.png -------------------------------------------------------------------------------- /icons/control_play_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/control_play_blue.png -------------------------------------------------------------------------------- /icons/control_rewind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/control_rewind.png -------------------------------------------------------------------------------- /icons/control_rewind_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/control_rewind_blue.png -------------------------------------------------------------------------------- /icons/control_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/control_start.png -------------------------------------------------------------------------------- /icons/control_start_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/control_start_blue.png -------------------------------------------------------------------------------- /icons/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/cross.png -------------------------------------------------------------------------------- /icons/cross_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/cross_hover.png -------------------------------------------------------------------------------- /icons/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/delete.png -------------------------------------------------------------------------------- /icons/door_out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/door_out.png -------------------------------------------------------------------------------- /icons/feed.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/feed.ico -------------------------------------------------------------------------------- /icons/feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/feed.png -------------------------------------------------------------------------------- /icons/feed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /icons/feed32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/feed32.png -------------------------------------------------------------------------------- /icons/feed_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/feed_add.png -------------------------------------------------------------------------------- /icons/feed_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/feed_delete.png -------------------------------------------------------------------------------- /icons/feed_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/feed_disabled.png -------------------------------------------------------------------------------- /icons/feed_go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/feed_go.png -------------------------------------------------------------------------------- /icons/filter32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/filter32.png -------------------------------------------------------------------------------- /icons/info32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/info32.png -------------------------------------------------------------------------------- /icons/information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/information.png -------------------------------------------------------------------------------- /icons/transmit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/transmit.png -------------------------------------------------------------------------------- /icons/unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/icons/unchecked.png -------------------------------------------------------------------------------- /idle.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.platform == 'win32': 4 | from ctypes import * 5 | 6 | class LASTINPUTINFO(Structure): 7 | _fields_ = [ 8 | ('cbSize', c_uint), 9 | ('dwTime', c_int), 10 | ] 11 | 12 | def get_idle_duration(): 13 | lastInputInfo = LASTINPUTINFO() 14 | lastInputInfo.cbSize = sizeof(lastInputInfo) 15 | if windll.user32.GetLastInputInfo(byref(lastInputInfo)): 16 | millis = windll.kernel32.GetTickCount() - lastInputInfo.dwTime 17 | return millis / 1000.0 18 | else: 19 | return 0 20 | else: 21 | def get_idle_duration(): 22 | return 0 23 | 24 | if __name__ == '__main__': 25 | import time 26 | while True: 27 | duration = get_idle_duration() 28 | print 'User idle for %.2f seconds.' % duration 29 | time.sleep(1) 30 | -------------------------------------------------------------------------------- /install.bat: -------------------------------------------------------------------------------- 1 | installer\feed-notifier-2.6.exe /sp- /silent /norestart 2 | -------------------------------------------------------------------------------- /installer.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | [Setup] 5 | ; NOTE: The value of AppId uniquely identifies this application. 6 | ; Do not use the same AppId value in installers for other applications. 7 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 8 | AppId={{6091F327-2B13-4193-A6F1-4B2271613A74} 9 | AppName=Feed Notifier 10 | AppVerName=Feed Notifier 2.6 11 | AppPublisher=Michael Fogleman 12 | AppPublisherURL=http://www.feed-notifier.com/ 13 | AppSupportURL=http://www.feed-notifier.com/ 14 | AppUpdatesURL=http://www.feed-notifier.com/ 15 | DefaultDirName={pf}\Feed Notifier 16 | DefaultGroupName=Feed Notifier 17 | AllowNoIcons=yes 18 | OutputDir=installer 19 | OutputBaseFilename=feed-notifier-2.6 20 | Compression=lzma 21 | SolidCompression=yes 22 | 23 | [Languages] 24 | Name: "english"; MessagesFile: "compiler:Default.isl" 25 | 26 | [Tasks] 27 | Name: "startupicon"; Description: "Run Feed Notifier on Startup"; GroupDescription: "Additional tasks:"; 28 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 29 | Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 30 | 31 | [InstallDelete] 32 | Type: filesandordirs; Name: "{app}\images" 33 | Type: files; Name: "{app}\msvcp71.dll" 34 | Type: files; Name: "{app}\MSVCR71.dll" 35 | Type: files; Name: "{app}\notifier.exe.manifest" 36 | 37 | [Dirs] 38 | Name: "{app}"; Permissions: everyone-modify 39 | 40 | [Files] 41 | Source: "dist\notifier.exe"; DestDir: "{app}"; Flags: ignoreversion; Permissions: everyone-readexec 42 | Source: "dist\w9xpopen.exe"; DestDir: "{app}"; Flags: ignoreversion; Permissions: everyone-readexec 43 | Source: "dist\library.zip"; DestDir: "{app}"; Flags: ignoreversion; Permissions: everyone-readexec 44 | Source: "dist\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 45 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 46 | 47 | [Icons] 48 | Name: "{group}\Feed Notifier"; Filename: "{app}\notifier.exe"; WorkingDir: "{app}"; 49 | Name: "{group}\{cm:UninstallProgram,Feed Notifier}"; Filename: "{uninstallexe}" 50 | Name: "{userdesktop}\Feed Notifier"; Filename: "{app}\notifier.exe"; WorkingDir: "{app}"; Tasks: desktopicon 51 | Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\Feed Notifier"; Filename: "{app}\notifier.exe"; WorkingDir: "{app}"; Tasks: quicklaunchicon 52 | Name: "{userstartup}\Feed Notifier"; Filename: "{app}\notifier.exe"; WorkingDir: "{app}"; Tasks: startupicon 53 | 54 | [Run] 55 | Filename: "{app}\notifier.exe"; Description: "{cm:LaunchProgram,Feed Notifier}"; Flags: nowait postinstall 56 | 57 | 58 | -------------------------------------------------------------------------------- /ipc.py: -------------------------------------------------------------------------------- 1 | import wx 2 | import sys 3 | import util 4 | 5 | class CallbackContainer(object): 6 | def __init__(self): 7 | self.callback = None 8 | def __call__(self, message): 9 | if self.callback: 10 | wx.CallAfter(self.callback, message) 11 | 12 | if sys.platform == 'win32': 13 | import win32file 14 | import win32pipe 15 | import time 16 | 17 | def init(): 18 | container = CallbackContainer() 19 | message = '\n'.join(sys.argv[1:]) 20 | name = r'\\.\pipe\FeedNotifier_%s' % wx.GetUserId() 21 | if client(name, message): 22 | return None, message 23 | else: 24 | util.start_thread(server, name, container) 25 | return container, message 26 | 27 | def server(name, callback_func): 28 | buffer = 4096 29 | timeout = 1000 30 | error = False 31 | while True: 32 | if error: 33 | time.sleep(1) 34 | error = False 35 | handle = win32pipe.CreateNamedPipe( 36 | name, 37 | win32pipe.PIPE_ACCESS_INBOUND, 38 | win32pipe.PIPE_TYPE_BYTE | win32pipe.PIPE_READMODE_BYTE | win32pipe.PIPE_WAIT, 39 | win32pipe.PIPE_UNLIMITED_INSTANCES, 40 | buffer, 41 | buffer, 42 | timeout, 43 | None) 44 | if handle == win32file.INVALID_HANDLE_VALUE: 45 | error = True 46 | continue 47 | try: 48 | if win32pipe.ConnectNamedPipe(handle) != 0: 49 | error = True 50 | else: 51 | code, message = win32file.ReadFile(handle, buffer, None) 52 | if code == 0: 53 | callback_func(message) 54 | else: 55 | error = True 56 | except Exception: 57 | error = True 58 | finally: 59 | win32pipe.DisconnectNamedPipe(handle) 60 | win32file.CloseHandle(handle) 61 | 62 | def client(name, message): 63 | try: 64 | file = open(name, 'wb') 65 | file.write(message) 66 | file.close() 67 | return True 68 | except IOError: 69 | return False 70 | else: 71 | import functools 72 | import socket 73 | import SocketServer 74 | 75 | def init(): 76 | container = CallbackContainer() 77 | message = '\n'.join(sys.argv[1:]) 78 | host, port = 'localhost', 31763 79 | try: 80 | server(host, port, container) 81 | return container, message 82 | except socket.error: 83 | client(host, port, message) 84 | return None, message 85 | 86 | def server(host, port, callback_func): 87 | class Handler(SocketServer.StreamRequestHandler): 88 | def __init__(self, callback_func, *args, **kwargs): 89 | self.callback_func = callback_func 90 | SocketServer.StreamRequestHandler.__init__(self, *args, **kwargs) 91 | def handle(self): 92 | data = self.rfile.readline().strip() 93 | self.callback_func(data) 94 | server = SocketServer.TCPServer((host, port), functools.partial(Handler, callback_func)) 95 | util.start_thread(server.serve_forever) 96 | 97 | def client(host, port, message): 98 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 99 | sock.connect((host, port)) 100 | sock.send(message) 101 | sock.close() 102 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Michael Fogleman 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the organization nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | def init_path(): 2 | import os 3 | import dummy 4 | file = dummy.__file__ 5 | file = os.path.abspath(file) 6 | while file and not os.path.isdir(file): 7 | file, ext = os.path.split(file) 8 | os.chdir(file) 9 | 10 | def init_logging(): 11 | import sys 12 | import logging 13 | logging.basicConfig( 14 | level=logging.DEBUG, 15 | filename='log.txt', 16 | filemode='w', 17 | format='%(asctime)s %(levelname)s %(message)s', 18 | datefmt='%H:%M:%S', 19 | ) 20 | if not hasattr(sys, 'frozen'): 21 | console = logging.StreamHandler(sys.stdout) 22 | console.setLevel(logging.DEBUG) 23 | formatter = logging.Formatter( 24 | '%(asctime)s %(levelname)s %(message)s', 25 | '%H:%M:%S', 26 | ) 27 | console.setFormatter(formatter) 28 | logging.getLogger('').addHandler(console) 29 | 30 | def main(): 31 | init_path() 32 | init_logging() 33 | import wx 34 | import ipc 35 | import controller 36 | container, message = ipc.init() 37 | if not container: 38 | return 39 | app = wx.PySimpleApp()#redirect=True, filename='log.txt') 40 | wx.Log_SetActiveTarget(wx.LogStderr()) 41 | ctrl = controller.Controller() 42 | container.callback = ctrl.parse_args 43 | container(message) 44 | app.MainLoop() 45 | 46 | if __name__ == '__main__': 47 | main() 48 | -------------------------------------------------------------------------------- /parsetab.py: -------------------------------------------------------------------------------- 1 | 2 | # parsetab.py 3 | # This file is automatically generated. Do not edit. 4 | _tabversion = '3.2' 5 | 6 | _lr_method = 'LALR' 7 | 8 | _lr_signature = '\x03\xd8\xc9Q1\x0e\x13W\xf5\xf7\xacu\x8b$z\xd4' 9 | 10 | _lr_action_items = {'AND':([2,8,16,17,20,21,22,23,],[-2,18,18,-5,-7,-6,-3,18,]),'WORD':([0,1,3,5,6,7,9,10,11,12,13,14,15,18,19,],[-16,-16,-9,-8,-16,-16,-10,20,-13,-11,-14,-12,-15,-16,-16,]),'AUTHOR':([0,1,3,5,6,7,9,18,19,],[-16,11,-9,-8,-16,-16,-10,-16,-16,]),'TITLE':([0,1,3,5,6,7,9,18,19,],[-16,12,-9,-8,-16,-16,-10,-16,-16,]),'OR':([2,8,16,17,20,21,22,23,],[-2,19,19,-5,-7,-6,-3,-4,]),'CONTENT':([0,1,3,5,6,7,9,18,19,],[-16,13,-9,-8,-16,-16,-10,-16,-16,]),'LINK':([0,1,3,5,6,7,9,18,19,],[-16,14,-9,-8,-16,-16,-10,-16,-16,]),'LPAREN':([0,6,7,18,19,],[6,6,6,6,6,]),'NOT':([0,6,7,18,19,],[7,7,7,7,7,]),'PLUS':([0,6,7,18,19,],[5,5,5,5,5,]),'$end':([2,4,8,17,20,21,22,23,],[-2,0,-1,-5,-7,-6,-3,-4,]),'MINUS':([0,6,7,18,19,],[3,3,3,3,3,]),'RPAREN':([2,16,17,20,21,22,23,],[-2,21,-5,-7,-6,-3,-4,]),} 11 | 12 | _lr_action = { } 13 | for _k, _v in _lr_action_items.items(): 14 | for _x,_y in zip(_v[0],_v[1]): 15 | if not _x in _lr_action: _lr_action[_x] = { } 16 | _lr_action[_x][_k] = _y 17 | del _lr_action_items 18 | 19 | _lr_goto_items = {'qualifier':([1,],[10,]),'type':([0,6,7,18,19,],[1,1,1,1,1,]),'rule':([0,6,7,18,19,],[2,2,2,2,2,]),'filter':([0,],[4,]),'expression':([0,6,7,18,19,],[8,16,17,22,23,]),'empty':([0,1,6,7,18,19,],[9,15,9,9,9,9,]),} 20 | 21 | _lr_goto = { } 22 | for _k, _v in _lr_goto_items.items(): 23 | for _x,_y in zip(_v[0],_v[1]): 24 | if not _x in _lr_goto: _lr_goto[_x] = { } 25 | _lr_goto[_x][_k] = _y 26 | del _lr_goto_items 27 | _lr_productions = [ 28 | ("S' -> filter","S'",1,None,None,None), 29 | ('filter -> expression','filter',1,'p_filter','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',161), 30 | ('expression -> rule','expression',1,'p_expression_rule','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',165), 31 | ('expression -> expression AND expression','expression',3,'p_expression_and','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',169), 32 | ('expression -> expression OR expression','expression',3,'p_expression_or','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',173), 33 | ('expression -> NOT expression','expression',2,'p_expression_not','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',177), 34 | ('expression -> LPAREN expression RPAREN','expression',3,'p_expression_group','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',181), 35 | ('rule -> type qualifier WORD','rule',3,'p_rule','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',185), 36 | ('type -> PLUS','type',1,'p_type','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',189), 37 | ('type -> MINUS','type',1,'p_type','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',190), 38 | ('type -> empty','type',1,'p_type','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',191), 39 | ('qualifier -> TITLE','qualifier',1,'p_qualifier','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',195), 40 | ('qualifier -> LINK','qualifier',1,'p_qualifier','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',196), 41 | ('qualifier -> AUTHOR','qualifier',1,'p_qualifier','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',197), 42 | ('qualifier -> CONTENT','qualifier',1,'p_qualifier','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',198), 43 | ('qualifier -> empty','qualifier',1,'p_qualifier','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',199), 44 | ('empty -> ','empty',0,'p_empty','C:\\Documents and Settings\\Michael Fogleman\\My Documents\\Workspace\\Feed Notifier 2\\filters.py',203), 45 | ] 46 | -------------------------------------------------------------------------------- /popups.py: -------------------------------------------------------------------------------- 1 | import wx 2 | import webbrowser 3 | from settings import settings 4 | 5 | BLANK = 'about:blank' 6 | COMMAND_CLOSE = 'http://close/' 7 | COMMAND_NEXT = 'http://next/' 8 | COMMAND_PREVIOUS = 'http://previous/' 9 | COMMAND_FIRST = 'http://first/' 10 | COMMAND_LAST = 'http://last/' 11 | COMMAND_PLAY = 'http://play/' 12 | COMMAND_PAUSE = 'http://pause/' 13 | 14 | def position_window(window): 15 | index = settings.POPUP_DISPLAY 16 | if index >= wx.Display_GetCount(): 17 | index = 0 18 | display = wx.Display(index) 19 | x, y, w, h = display.GetClientArea() 20 | cw, ch = window.GetSize() 21 | pad = 10 22 | x1 = x + pad 23 | y1 = y + pad 24 | x2 = x + w - cw - pad 25 | y2 = y + h - ch - pad 26 | x3 = x + w / 2 - cw / 2 27 | y3 = y + h / 2 - ch / 2 28 | lookup = { 29 | (-1, -1): (x1, y1), 30 | (1, -1): (x2, y1), 31 | (-1, 1): (x1, y2), 32 | (1, 1): (x2, y2), 33 | (0, 0): (x3, y3), 34 | } 35 | window.SetPosition(lookup[settings.POPUP_POSITION]) 36 | 37 | class Event(wx.PyEvent): 38 | def __init__(self, event_object, type): 39 | super(Event, self).__init__() 40 | self.SetEventType(type.typeId) 41 | self.SetEventObject(event_object) 42 | 43 | EVT_LINK = wx.PyEventBinder(wx.NewEventType()) 44 | EVT_POPUP_CLOSE = wx.PyEventBinder(wx.NewEventType()) 45 | EVT_POPUP_ENTER = wx.PyEventBinder(wx.NewEventType()) 46 | EVT_POPUP_LEAVE = wx.PyEventBinder(wx.NewEventType()) 47 | 48 | class PopupManager(wx.EvtHandler): 49 | def __init__(self): 50 | super(PopupManager, self).__init__() 51 | self.timer = None 52 | self.auto = settings.POPUP_AUTO_PLAY 53 | self.cache = {} 54 | self.hover_count = 0 55 | def set_items(self, items, index=0, focus=False): 56 | self.items = list(items) 57 | self.index = index 58 | self.count = len(self.items) 59 | self.clear_cache(keep_current_item=True) 60 | self.update(focus) 61 | self.set_timer() 62 | def update(self, focus=False): 63 | item = self.items[self.index] 64 | if item in self.cache: 65 | self.show_frame(focus) 66 | self.update_cache() 67 | else: 68 | self.update_cache(True) 69 | self.show_frame(focus) 70 | self.update_cache() 71 | def update_cache(self, current_only=False): 72 | indexes = set() 73 | indexes.add(self.index) 74 | if not current_only: 75 | indexes.add(self.index - 1) 76 | indexes.add(self.index + 1) 77 | #indexes.add(0) 78 | #indexes.add(self.count - 1) 79 | items = set(self.items[index] for index in indexes if index >= 0 and index < self.count) 80 | for item in items: 81 | if item in self.cache: 82 | continue 83 | frame = self.create_frame(item) 84 | self.cache[item] = frame 85 | for item, frame in self.cache.items(): 86 | if item not in items: 87 | frame.Close() 88 | del self.cache[item] 89 | def clear_cache(self, keep_current_item=False): 90 | current_item = self.items[self.index] 91 | for item, frame in self.cache.items(): 92 | if keep_current_item and item == current_item: 93 | continue 94 | frame.Close() 95 | del self.cache[item] 96 | def show_frame(self, focus=False): 97 | current_item = self.items[self.index] 98 | current_item.read = True 99 | for item, frame in self.cache.items(): 100 | if item == current_item: 101 | if focus: 102 | frame.Show() 103 | else: 104 | frame.Disable() 105 | frame.Show() 106 | frame.Enable() 107 | frame.Update() 108 | if settings.POPUP_TRANSPARENCY < 255: 109 | frame.SetTransparent(settings.POPUP_TRANSPARENCY) 110 | for item, frame in self.cache.items(): 111 | if item != current_item: 112 | frame.Hide() 113 | def create_frame(self, item): 114 | if True:#settings.POPUP_THEME == 'default': 115 | import theme_default 116 | context = self.create_context(item) 117 | frame = theme_default.Frame(item, context) 118 | frame.Bind(EVT_LINK, self.on_link) 119 | frame.Bind(EVT_POPUP_ENTER, self.on_enter) 120 | frame.Bind(EVT_POPUP_LEAVE, self.on_leave) 121 | position_window(frame) 122 | if settings.POPUP_TRANSPARENCY < 255: 123 | frame.SetTransparent(0) 124 | return frame 125 | def create_context(self, item): 126 | context = {} 127 | count = str(self.count) 128 | index = str(self.items.index(item) + 1) 129 | index = '%s%s' % ('0' * (len(count) - len(index)), index) 130 | context['item_index'] = index 131 | context['item_count'] = count 132 | context['is_playing'] = self.auto 133 | context['is_paused'] = not self.auto 134 | context['POPUP_WIDTH'] = settings.POPUP_WIDTH 135 | context['COMMAND_CLOSE'] = COMMAND_CLOSE 136 | context['COMMAND_NEXT'] = COMMAND_NEXT 137 | context['COMMAND_PREVIOUS'] = COMMAND_PREVIOUS 138 | context['COMMAND_FIRST'] = COMMAND_FIRST 139 | context['COMMAND_LAST'] = COMMAND_LAST 140 | context['COMMAND_PLAY'] = COMMAND_PLAY 141 | context['COMMAND_PAUSE'] = COMMAND_PAUSE 142 | return context 143 | def set_timer(self): 144 | if self.timer and self.timer.IsRunning(): 145 | return 146 | duration = settings.POPUP_DURATION * 1000 147 | self.timer = wx.CallLater(duration, self.on_timer) 148 | def stop_timer(self): 149 | if self.timer and self.timer.IsRunning(): 150 | self.timer.Stop() 151 | self.timer = None 152 | def on_enter(self, event): 153 | event.Skip() 154 | self.hover_count += 1 155 | def on_leave(self, event): 156 | event.Skip() 157 | self.hover_count -= 1 158 | def on_link(self, event): 159 | link = event.link 160 | # track the click 161 | item = self.items[self.index] 162 | feed = item.feed 163 | if link == item.link or link == feed.link: 164 | feed.clicks += 1 165 | # handle the click 166 | if link == BLANK: 167 | event.Skip() 168 | elif link == COMMAND_CLOSE: 169 | self.on_close() 170 | elif link == COMMAND_FIRST: 171 | self.auto = False 172 | self.on_first() 173 | elif link == COMMAND_LAST: 174 | self.auto = False 175 | self.on_last() 176 | elif link == COMMAND_NEXT: 177 | self.auto = False 178 | self.on_next() 179 | elif link == COMMAND_PREVIOUS: 180 | self.auto = False 181 | self.on_previous() 182 | elif link == COMMAND_PLAY: 183 | if not self.auto: 184 | self.auto = True 185 | self.stop_timer() 186 | self.on_timer() 187 | elif link == COMMAND_PAUSE: 188 | self.auto = False 189 | else: 190 | webbrowser.open(link) 191 | def on_first(self): 192 | self.index = 0 193 | self.update(True) 194 | def on_last(self): 195 | self.index = self.count - 1 196 | self.update(True) 197 | def on_next(self, focus=True): 198 | if self.index < self.count - 1: 199 | self.index += 1 200 | self.update(focus) 201 | else: 202 | self.on_close() 203 | def on_previous(self): 204 | if self.index > 0: 205 | self.index -= 1 206 | self.update(True) 207 | def on_close(self): 208 | self.stop_timer() 209 | self.clear_cache() 210 | event = Event(self, EVT_POPUP_CLOSE) 211 | wx.PostEvent(self, event) 212 | def on_timer(self): 213 | self.timer = None 214 | set_timer = False 215 | if self.hover_count and settings.POPUP_WAIT_ON_HOVER: 216 | set_timer = True 217 | elif self.auto: 218 | if self.index == self.count - 1: 219 | self.on_close() 220 | else: 221 | self.on_next(False) 222 | set_timer = True 223 | if set_timer: 224 | self.set_timer() 225 | -------------------------------------------------------------------------------- /safe_pickle.py: -------------------------------------------------------------------------------- 1 | import os 2 | import cPickle as pickle 3 | 4 | def load(path): 5 | tmp_path = '%s.tmp' % path 6 | bak_path = '%s.bak' % path 7 | for p in (path, bak_path, tmp_path): 8 | try: 9 | with open(p, 'rb') as file: 10 | return pickle.load(file) 11 | except Exception: 12 | pass 13 | raise Exception('Unable to load: %s' % path) 14 | 15 | def save(path, data): 16 | tmp_path = '%s.tmp' % path 17 | bak_path = '%s.bak' % path 18 | # Write tmp file 19 | with open(tmp_path, 'wb') as file: 20 | pickle.dump(data, file, -1) 21 | # Copy existing file to bak file 22 | try: 23 | os.remove(bak_path) 24 | except Exception: 25 | pass 26 | try: 27 | os.rename(path, bak_path) 28 | except Exception: 29 | pass 30 | # Rename tmp file to actual file 31 | os.rename(tmp_path, path) 32 | # Remove bak file 33 | try: 34 | os.remove(bak_path) 35 | except Exception: 36 | pass 37 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | import safe_pickle 2 | 3 | class InvalidSettingError(Exception): 4 | pass 5 | 6 | class NOT_SET(object): 7 | pass 8 | 9 | class Settings(object): 10 | def __init__(self, parent): 11 | self._parent = parent 12 | def __getattr__(self, name): 13 | if name.startswith('_'): 14 | return super(Settings, self).__getattr__(name) 15 | value = self.get(name) 16 | if value != NOT_SET: 17 | return value 18 | if self._parent: 19 | return getattr(self._parent, name) 20 | raise InvalidSettingError, 'Invalid setting: %s' % name 21 | def __setattr__(self, name, value): 22 | if name.startswith('_'): 23 | super(Settings, self).__setattr__(name, value) 24 | return 25 | if self.set(name, value): 26 | return 27 | if self._parent: 28 | setattr(self._parent, name, value) 29 | return 30 | raise InvalidSettingError, 'Invalid setting: %s' % name 31 | def get(self, name): 32 | raise NotImplementedError, 'Settings subclasses must implement the get() method.' 33 | def set(self, name, value): 34 | raise NotImplementedError, 'Settings subclasses must implement the set() method.' 35 | 36 | class ModuleSettings(Settings): 37 | def __init__(self, parent, module): 38 | super(ModuleSettings, self).__init__(parent) 39 | self._module = module 40 | def get(self, name): 41 | module = self._module 42 | if hasattr(module, name): 43 | return getattr(module, name) 44 | return NOT_SET 45 | def set(self, name, value): 46 | return False 47 | 48 | class FileSettings(Settings): 49 | def __init__(self, parent, file): 50 | super(FileSettings, self).__init__(parent) 51 | self._file = file 52 | self.load() 53 | def load(self): 54 | try: 55 | self._settings = safe_pickle.load(self._file) 56 | except Exception: 57 | self._settings = {} 58 | def save(self): 59 | safe_pickle.save(self._file, self._settings) 60 | def get(self, name): 61 | if name in self._settings: 62 | return self._settings[name] 63 | return NOT_SET 64 | def set(self, name, value): 65 | if value != getattr(self, name): 66 | self._settings[name] = value 67 | self.save() 68 | return True 69 | 70 | def create_chain(): 71 | import defaults 72 | settings = ModuleSettings(None, defaults) 73 | settings = FileSettings(settings, 'settings.dat') 74 | return settings 75 | 76 | settings = create_chain() 77 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import py2exe 3 | import sys 4 | from distutils.core import setup 5 | 6 | manifest = ''' 7 | 9 | 15 | Feed Notifier 2.6 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | 39 | 47 | 48 | 49 | 50 | ''' 51 | 52 | # Don't require the command line argument. 53 | sys.argv.append('py2exe') 54 | 55 | # Include these data files. 56 | def get_data_files(): 57 | def filter_files(files): 58 | def match(file): 59 | extensions = ['.dat'] 60 | for extension in extensions: 61 | if file.endswith(extension): 62 | return True 63 | return False 64 | return tuple(file for file in files if not match(file)) 65 | def tree(src): 66 | return [(root, map(lambda f: os.path.join(root, f), filter_files(files))) for (root, dirs, files) in os.walk(os.path.normpath(src))] 67 | def include(src): 68 | result = tree(src) 69 | result = [('.', item[1]) for item in result] 70 | return result 71 | data_files = [] 72 | data_files += tree('./icons') 73 | data_files += tree('./sounds') 74 | data_files += tree('./Microsoft.VC90.CRT') 75 | return data_files 76 | 77 | # Build the distribution. 78 | setup( 79 | options = {"py2exe":{ 80 | "compressed": 1, 81 | "optimize": 1, 82 | "bundle_files": 1, 83 | "includes": ['parsetab'], 84 | "dll_excludes": [ 85 | 'msvcp90.dll', 86 | 'mswsock.dll', 87 | 'API-MS-Win-Core-LocalRegistry-L1-1-0.dll', 88 | 'API-MS-Win-Core-ProcessThreads-L1-1-0.dll', 89 | 'API-MS-Win-Security-Base-L1-1-0.dll', 90 | 'POWRPROF.dll', 91 | 'Secur32.dll', 92 | 'SHFOLDER.dll', 93 | ], 94 | }}, 95 | windows = [{ 96 | "script": "main.py", 97 | "dest_base": "notifier", 98 | "icon_resources": [(1, "icons/feed.ico")], 99 | "other_resources": [(24, 1, manifest)], 100 | }], 101 | data_files = get_data_files(), 102 | ) 103 | 104 | # Build Information 105 | def get_revision(): 106 | import time 107 | return int(time.time()) 108 | 109 | def save_build_info(): 110 | revision = get_revision() 111 | path = 'dist/revision.txt' 112 | with open(path, 'w') as file: 113 | file.write(str(revision)) 114 | print 115 | print 'Saved build revision %d to %s' % (revision, path) 116 | 117 | save_build_info() 118 | -------------------------------------------------------------------------------- /sounds/notification.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/sounds/notification.wav -------------------------------------------------------------------------------- /theme_default.py: -------------------------------------------------------------------------------- 1 | import wx 2 | import controls 3 | import popups 4 | import util 5 | from settings import settings 6 | 7 | BACKGROUND = (230, 230, 230) 8 | 9 | class Frame(wx.Frame): 10 | def __init__(self, item, context): 11 | title = settings.APP_NAME 12 | style = wx.FRAME_NO_TASKBAR | wx.BORDER_NONE 13 | if settings.POPUP_STAY_ON_TOP: 14 | style |= wx.STAY_ON_TOP 15 | super(Frame, self).__init__(None, -1, title, style=style) 16 | self.item = item 17 | self.context = context 18 | self.hover_count = 0 19 | container = self.create_container(self) 20 | container.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel) 21 | container.Bind(wx.EVT_KEY_DOWN, self.on_key_down) 22 | self.container = container 23 | self.Fit() 24 | def post_link(self, link): 25 | event = popups.Event(self, popups.EVT_LINK) 26 | event.link = link 27 | wx.PostEvent(self, event) 28 | def on_link(self, event): 29 | self.post_link(event.link) 30 | def on_left_down(self, event): 31 | self.post_link(popups.COMMAND_NEXT) 32 | def on_mousewheel(self, event): 33 | if event.GetWheelRotation() < 0: 34 | self.post_link(popups.COMMAND_NEXT) 35 | else: 36 | self.post_link(popups.COMMAND_PREVIOUS) 37 | def on_focus(self, event): 38 | if event.GetEventObject() != self.container: 39 | self.container.SetFocusIgnoringChildren() 40 | def on_key_down(self, event): 41 | code = event.GetKeyCode() 42 | if code == wx.WXK_ESCAPE: 43 | self.post_link(popups.COMMAND_CLOSE) 44 | elif code == wx.WXK_LEFT: 45 | self.post_link(popups.COMMAND_PREVIOUS) 46 | elif code == wx.WXK_RIGHT: 47 | self.post_link(popups.COMMAND_NEXT) 48 | elif code == wx.WXK_HOME: 49 | self.post_link(popups.COMMAND_FIRST) 50 | elif code == wx.WXK_END: 51 | self.post_link(popups.COMMAND_LAST) 52 | def on_enter(self, event): 53 | event.Skip() 54 | self.hover_count += 1 55 | if self.hover_count == 1: 56 | wx.PostEvent(self, popups.Event(self, popups.EVT_POPUP_ENTER)) 57 | def on_leave(self, event): 58 | event.Skip() 59 | self.hover_count -= 1 60 | if self.hover_count == 0: 61 | wx.PostEvent(self, popups.Event(self, popups.EVT_POPUP_LEAVE)) 62 | def bind_links(self, widgets): 63 | for widget in widgets: 64 | widget.Bind(controls.EVT_HYPERLINK, self.on_link) 65 | widget.Bind(wx.EVT_SET_FOCUS, self.on_focus) 66 | widget.Bind(wx.EVT_ENTER_WINDOW, self.on_enter) 67 | widget.Bind(wx.EVT_LEAVE_WINDOW, self.on_leave) 68 | def bind_widgets(self, widgets): 69 | for widget in widgets: 70 | widget.Bind(wx.EVT_LEFT_DOWN, self.on_left_down) 71 | widget.Bind(wx.EVT_SET_FOCUS, self.on_focus) 72 | widget.Bind(wx.EVT_ENTER_WINDOW, self.on_enter) 73 | widget.Bind(wx.EVT_LEAVE_WINDOW, self.on_leave) 74 | def create_container(self, parent): 75 | color = self.item.feed.color or settings.POPUP_BORDER_COLOR 76 | 77 | panel1 = wx.Panel(parent, -1, style=wx.WANTS_CHARS) 78 | panel1.SetBackgroundColour(wx.Colour(*color)) 79 | panel1.SetForegroundColour(wx.Colour(*color)) 80 | panel2 = wx.Panel(panel1, -1) 81 | panel2.SetBackgroundColour(wx.BLACK) 82 | panel2.SetForegroundColour(wx.BLACK) 83 | panel3 = wx.Panel(panel2, -1) 84 | panel3.SetBackgroundColour(wx.WHITE) 85 | panel3.SetForegroundColour(wx.BLACK) 86 | contents = self.create_contents(panel3) 87 | 88 | sizer = wx.BoxSizer(wx.VERTICAL) 89 | sizer.Add(panel2, 1, wx.EXPAND|wx.ALL, settings.POPUP_BORDER_SIZE) 90 | panel1.SetSizer(sizer) 91 | sizer = wx.BoxSizer(wx.VERTICAL) 92 | sizer.Add(panel3, 1, wx.EXPAND|wx.ALL, 1) 93 | panel2.SetSizer(sizer) 94 | sizer = wx.BoxSizer(wx.VERTICAL) 95 | sizer.Add(contents, 1, wx.EXPAND|wx.ALL) 96 | panel3.SetSizer(sizer) 97 | 98 | panel1.Fit() 99 | self.bind_widgets([panel1, panel2, panel3]) 100 | return panel1 101 | def create_contents(self, parent): 102 | header = self.create_header(parent) 103 | body = self.create_body(parent) 104 | footer = self.create_footer(parent) 105 | pen = wx.Pen(wx.BLACK, style=wx.USER_DASH) 106 | pen.SetDashes([0, 2]) 107 | line1 = controls.Line(parent, pen) 108 | line2 = controls.Line(parent, pen) 109 | sizer = wx.BoxSizer(wx.VERTICAL) 110 | sizer.Add(header, 0, wx.EXPAND) 111 | sizer.Add(line1, 0, wx.EXPAND) 112 | sizer.Add(body, 1, wx.EXPAND) 113 | sizer.Add(line2, 0, wx.EXPAND) 114 | sizer.Add(footer, 0, wx.EXPAND) 115 | self.bind_widgets([line1, line2]) 116 | return sizer 117 | def create_header(self, parent): 118 | panel = wx.Panel(parent, -1) 119 | panel.SetBackgroundColour(wx.Colour(*BACKGROUND)) 120 | panel.SetForegroundColour(wx.BLACK) 121 | feed = self.item.feed 122 | paths = ['icons/feed.png'] 123 | if feed.has_favicon: 124 | paths.insert(0, feed.favicon_path) 125 | for path in paths: 126 | try: 127 | bitmap = util.scale_bitmap(wx.Bitmap(path), 16, 16, wx.Colour(*BACKGROUND)) 128 | break 129 | except Exception: 130 | pass 131 | else: 132 | bitmap = wx.EmptyBitmap(16, 16) 133 | icon = controls.BitmapLink(panel, feed.link, bitmap) 134 | icon.SetBackgroundColour(wx.Colour(*BACKGROUND)) 135 | width, height = icon.GetSize() 136 | feed = self.create_feed(panel, width) 137 | button = controls.BitmapLink(panel, popups.COMMAND_CLOSE, wx.Bitmap('icons/cross.png'), wx.Bitmap('icons/cross_hover.png')) 138 | button.SetBackgroundColour(wx.Colour(*BACKGROUND)) 139 | sizer = wx.BoxSizer(wx.HORIZONTAL) 140 | sizer.Add(icon, 0, wx.ALIGN_CENTER|wx.ALL, 10) 141 | sizer.Add(feed, 1, wx.ALIGN_CENTER_VERTICAL|wx.TOP|wx.BOTTOM, 5) 142 | sizer.Add(button, 0, wx.ALIGN_CENTER|wx.ALL, 10) 143 | panel.SetSizer(sizer) 144 | self.bind_links([icon, button]) 145 | self.bind_widgets([panel]) 146 | return panel 147 | def create_feed(self, parent, icon_width): 148 | width = settings.POPUP_WIDTH - 64 - icon_width 149 | if self.item.feed.link: 150 | link = controls.Link(parent, width, self.item.feed.link, self.item.feed.title) 151 | else: 152 | link = controls.Text(parent, width, self.item.feed.title) 153 | link.SetBackgroundColour(wx.Colour(*BACKGROUND)) 154 | font = link.GetFont() 155 | font.SetWeight(wx.BOLD) 156 | link.SetFont(font) 157 | if self.item.author: 158 | info = '%s ago by %s' % (self.item.time_since, self.item.author) 159 | else: 160 | info = '%s ago' % self.item.time_since 161 | info = controls.Text(parent, width, info) 162 | info.SetBackgroundColour(wx.Colour(*BACKGROUND)) 163 | sizer = wx.BoxSizer(wx.VERTICAL) 164 | sizer.Add(link, 0, wx.EXPAND) 165 | sizer.Add(info, 0, wx.EXPAND) 166 | self.bind_links([link]) 167 | self.bind_widgets([info]) 168 | return sizer 169 | def create_body(self, parent): 170 | width = settings.POPUP_WIDTH - 28 171 | if self.item.link: 172 | link = controls.Link(parent, width, self.item.link, self.item.title) 173 | else: 174 | link = controls.Text(parent, width, self.item.title) 175 | link.SetBackgroundColour(wx.WHITE) 176 | font = link.GetFont() 177 | font.SetWeight(wx.BOLD) 178 | font.SetPointSize(12) 179 | link.SetFont(font) 180 | text = controls.Text(parent, width, self.item.description) 181 | text.SetBackgroundColour(wx.WHITE) 182 | font = text.GetFont() 183 | font.SetPointSize(10) 184 | text.SetFont(font) 185 | sizer = wx.BoxSizer(wx.VERTICAL) 186 | sizer.AddSpacer(5) 187 | sizer.Add(link, 0, wx.EXPAND|wx.LEFT|wx.RIGHT, 10) 188 | sizer.AddSpacer(5) 189 | sizer.Add(text, 0, wx.EXPAND|wx.LEFT|wx.RIGHT, 10) 190 | sizer.AddSpacer(10) 191 | self.bind_links([link]) 192 | self.bind_widgets([text]) 193 | return sizer 194 | def create_footer(self, parent): 195 | panel = wx.Panel(parent, -1) 196 | panel.SetBackgroundColour(wx.Colour(*BACKGROUND)) 197 | panel.SetForegroundColour(wx.BLACK) 198 | first = controls.BitmapLink(panel, popups.COMMAND_FIRST, wx.Bitmap('icons/control_start.png'), wx.Bitmap('icons/control_start_blue.png')) 199 | previous = controls.BitmapLink(panel, popups.COMMAND_PREVIOUS, wx.Bitmap('icons/control_rewind.png'), wx.Bitmap('icons/control_rewind_blue.png')) 200 | text = '%s of %s' % (self.context['item_index'], self.context['item_count']) 201 | text = controls.Text(panel, 0, text) 202 | text.SetBackgroundColour(wx.Colour(*BACKGROUND)) 203 | text.fit_no_wrap() 204 | next = controls.BitmapLink(panel, popups.COMMAND_NEXT, wx.Bitmap('icons/control_fastforward.png'), wx.Bitmap('icons/control_fastforward_blue.png')) 205 | last = controls.BitmapLink(panel, popups.COMMAND_LAST, wx.Bitmap('icons/control_end.png'), wx.Bitmap('icons/control_end_blue.png')) 206 | play = controls.BitmapLink(panel, popups.COMMAND_PLAY, wx.Bitmap('icons/control_play.png'), wx.Bitmap('icons/control_play_blue.png')) 207 | pause = controls.BitmapLink(panel, popups.COMMAND_PAUSE, wx.Bitmap('icons/control_pause.png'), wx.Bitmap('icons/control_pause_blue.png')) 208 | widgets = [first, previous, next, last, play, pause] 209 | self.bind_links(widgets) 210 | for widget in widgets: 211 | widget.SetBackgroundColour(wx.Colour(*BACKGROUND)) 212 | sizer = wx.BoxSizer(wx.HORIZONTAL) 213 | sizer.AddSpacer(10) 214 | sizer.Add(first, 0, wx.TOP|wx.BOTTOM, 5) 215 | sizer.AddSpacer(8) 216 | sizer.Add(previous, 0, wx.TOP|wx.BOTTOM, 5) 217 | sizer.AddSpacer(8) 218 | sizer.Add(text, 0, wx.ALIGN_CENTER_VERTICAL|wx.TOP|wx.BOTTOM, 5) 219 | sizer.AddSpacer(8) 220 | sizer.Add(next, 0, wx.TOP|wx.BOTTOM, 5) 221 | sizer.AddSpacer(8) 222 | sizer.Add(last, 0, wx.TOP|wx.BOTTOM, 5) 223 | sizer.AddStretchSpacer(1) 224 | sizer.Add(play, 0, wx.TOP|wx.BOTTOM, 5) 225 | sizer.AddSpacer(8) 226 | sizer.Add(pause, 0, wx.TOP|wx.BOTTOM, 5) 227 | sizer.AddSpacer(10) 228 | panel.SetSizer(sizer) 229 | self.bind_widgets([panel, text]) 230 | return panel 231 | 232 | if __name__ == '__main__': 233 | app = wx.PySimpleApp() 234 | frame = Frame() 235 | frame.Show() 236 | app.MainLoop() 237 | -------------------------------------------------------------------------------- /themes/default/base.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Feed Notifier 8 | 9 | 10 | 13 | 14 | 15 | {% block body %} 16 | {% endblock %} 17 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /themes/default/control_end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/control_end.png -------------------------------------------------------------------------------- /themes/default/control_end_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/control_end_blue.png -------------------------------------------------------------------------------- /themes/default/control_fastforward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/control_fastforward.png -------------------------------------------------------------------------------- /themes/default/control_fastforward_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/control_fastforward_blue.png -------------------------------------------------------------------------------- /themes/default/control_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/control_pause.png -------------------------------------------------------------------------------- /themes/default/control_pause_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/control_pause_blue.png -------------------------------------------------------------------------------- /themes/default/control_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/control_play.png -------------------------------------------------------------------------------- /themes/default/control_play_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/control_play_blue.png -------------------------------------------------------------------------------- /themes/default/control_rewind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/control_rewind.png -------------------------------------------------------------------------------- /themes/default/control_rewind_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/control_rewind_blue.png -------------------------------------------------------------------------------- /themes/default/control_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/control_start.png -------------------------------------------------------------------------------- /themes/default/control_start_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/control_start_blue.png -------------------------------------------------------------------------------- /themes/default/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/cross.png -------------------------------------------------------------------------------- /themes/default/cross_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/cross_hover.png -------------------------------------------------------------------------------- /themes/default/feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/default/feed.png -------------------------------------------------------------------------------- /themes/default/index.html: -------------------------------------------------------------------------------- 1 | {% extends reldir ~ '/base.html' %} 2 | {% block body %} 3 |
4 |
5 | 21 |
22 |
23 | {{ item.title }} 24 |
25 |
26 | {{ item.description }} 27 |
28 |
29 | 40 |
41 |
42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /themes/default/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: Tahoma, Arial, sans-serif; 5 | cursor: default; 6 | } 7 | 8 | div { 9 | overflow: hidden; 10 | } 11 | 12 | a { 13 | color: #000; 14 | text-decoration: none; 15 | cursor: pointer; 16 | } 17 | 18 | a:hover { 19 | text-decoration: underline; 20 | } 21 | 22 | a img { 23 | border: none; 24 | } 25 | 26 | #container { 27 | width: {{ POPUP_WIDTH }}px; 28 | border: 3px solid #000; 29 | } 30 | 31 | #wrapper { 32 | border: 1px solid #fff; 33 | } 34 | 35 | #header { 36 | padding: 4px; 37 | background: #ddd; 38 | } 39 | 40 | #header img { 41 | margin: 4px; 42 | } 43 | 44 | #icon { 45 | float: left; 46 | } 47 | 48 | #feed { 49 | margin-left: 32px; 50 | margin-right: 32px; 51 | font-size: 8pt; 52 | } 53 | 54 | #feed-title { 55 | font-weight: bold; 56 | } 57 | 58 | #close { 59 | float: right; 60 | } 61 | 62 | #body { 63 | clear: both; 64 | padding: 8px; 65 | border-top: 1px dotted #000; 66 | border-bottom: 1px dotted #000; 67 | } 68 | 69 | #item-title { 70 | font-size: 12pt; 71 | font-weight: bold; 72 | margin-bottom: 6px; 73 | } 74 | 75 | #item-description { 76 | font-size: 10pt; 77 | } 78 | 79 | #footer { 80 | padding: 4px; 81 | background: #eee; 82 | font-size: 8pt; 83 | text-align: left; 84 | } 85 | 86 | #footer img { 87 | margin: 0 3px; 88 | vertical-align: middle; 89 | } 90 | 91 | #footer span { 92 | margin: 0 3px; 93 | } 94 | 95 | #footer #controls { 96 | float: right; 97 | } 98 | -------------------------------------------------------------------------------- /themes/minimal/base.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Feed Notifier 8 | 9 | 10 | 13 | 14 | 15 | {% block body %} 16 | {% endblock %} 17 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /themes/minimal/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/minimal/cross.png -------------------------------------------------------------------------------- /themes/minimal/index.html: -------------------------------------------------------------------------------- 1 | {% extends reldir ~ '/base.html' %} 2 | {% block body %} 3 |
4 |
5 | 6 |
7 | 10 |
11 | {{ item.title }} 12 |
13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /themes/minimal/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: Tahoma, Arial, sans-serif; 5 | cursor: default; 6 | } 7 | 8 | a { 9 | color: #000; 10 | text-decoration: none; 11 | cursor: pointer; 12 | } 13 | 14 | a:hover { 15 | text-decoration: underline; 16 | } 17 | 18 | a img { 19 | border: none; 20 | } 21 | 22 | #container { 23 | width: {{ POPUP_WIDTH }}px; 24 | border: 1px solid #000; 25 | padding: 8px; 26 | } 27 | 28 | #feed, #item { 29 | overflow: hidden; 30 | text-overflow: ellipsis; 31 | white-space: nowrap; 32 | } 33 | 34 | #feed { 35 | font-size: 10pt; 36 | font-weight: bold; 37 | margin-bottom: 4px; 38 | } 39 | 40 | #item { 41 | font-size: 10pt; 42 | float: left; 43 | } 44 | 45 | #close { 46 | float: right; 47 | } 48 | -------------------------------------------------------------------------------- /themes/simons_quest/arrow_down.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/simons_quest/arrow_down.gif -------------------------------------------------------------------------------- /themes/simons_quest/base.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Feed Notifier 8 | 9 | 10 | 13 | 14 | 15 | {% block body %} 16 | {% endblock %} 17 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /themes/simons_quest/bottom_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/simons_quest/bottom_left.png -------------------------------------------------------------------------------- /themes/simons_quest/bottom_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/simons_quest/bottom_right.png -------------------------------------------------------------------------------- /themes/simons_quest/horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/simons_quest/horizontal.png -------------------------------------------------------------------------------- /themes/simons_quest/index.html: -------------------------------------------------------------------------------- 1 | {% extends reldir ~ '/base.html' %} 2 | {% block body %} 3 | 4 | 5 | 9 | 10 | 24 | 25 | 26 | 27 | 29 | 30 | 34 |
6 | 7 | 8 |
11 | 12 | 13 | 14 |
15 | 16 |
17 |
18 | {{item.description}} 19 |
20 | 23 |
28 |
31 | 32 | 33 |
35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /themes/simons_quest/pixelette.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/simons_quest/pixelette.eot -------------------------------------------------------------------------------- /themes/simons_quest/pixelette.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/simons_quest/pixelette.ttf -------------------------------------------------------------------------------- /themes/simons_quest/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family:'pixelette'; 3 | src:url('{{absdir}}/pixelette.eot'); 4 | src: local('pixelette'), url('{{absdir}}/pixelette.ttf') format('truetype'); 5 | } 6 | 7 | body { 8 | margin: 0; 9 | padding: 0; 10 | cursor: default; 11 | font-family: pixelette; 12 | } 13 | 14 | #icon { 15 | margin: 8px; 16 | float: left; 17 | } 18 | 19 | #icon img { 20 | border: 0; 21 | filter:progid:DXImageTransform.Microsoft.Pixelate(maxsquare=4); 22 | } 23 | 24 | #item-title { 25 | font-size: 18px; 26 | margin-bottom: 6px; 27 | color: #ffffff; 28 | float: left; 29 | } 30 | 31 | #item-title a { 32 | text-decoration: none; 33 | color: #0056ff; 34 | } 35 | 36 | #item-description { 37 | clear:both; 38 | font-size: 12px; 39 | margin: 4px; 40 | } 41 | 42 | #footer #next { 43 | border: 0; 44 | float: right; 45 | margin-right: 8px; 46 | } 47 | -------------------------------------------------------------------------------- /themes/simons_quest/top_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/simons_quest/top_left.png -------------------------------------------------------------------------------- /themes/simons_quest/top_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/simons_quest/top_right.png -------------------------------------------------------------------------------- /themes/simons_quest/vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/themes/simons_quest/vertical.png -------------------------------------------------------------------------------- /updater.py: -------------------------------------------------------------------------------- 1 | import wx 2 | import os 3 | import time 4 | import urllib 5 | import tempfile 6 | import util 7 | from settings import settings 8 | 9 | class CancelException(Exception): 10 | pass 11 | 12 | class DownloadDialog(wx.Dialog): 13 | def __init__(self, parent): 14 | super(DownloadDialog, self).__init__(parent, -1, 'Feed Notifier Update') 15 | util.set_icon(self) 16 | self.path = None 17 | text = wx.StaticText(self, -1, 'Downloading update, please wait...') 18 | self.gauge = wx.Gauge(self, -1, 100, size=(250, 16)) 19 | cancel = wx.Button(self, wx.ID_CANCEL, 'Cancel') 20 | sizer = wx.BoxSizer(wx.VERTICAL) 21 | sizer.Add(text) 22 | sizer.AddSpacer(8) 23 | sizer.Add(self.gauge, 0, wx.EXPAND) 24 | sizer.AddSpacer(8) 25 | sizer.Add(cancel, 0, wx.ALIGN_RIGHT) 26 | wrapper = wx.BoxSizer(wx.VERTICAL) 27 | wrapper.Add(sizer, 1, wx.EXPAND|wx.ALL, 10) 28 | self.SetSizerAndFit(wrapper) 29 | self.start_download() 30 | def start_download(self): 31 | util.start_thread(self.download) 32 | def download(self): 33 | try: 34 | self.path = download_installer(self.listener) 35 | wx.CallAfter(self.EndModal, wx.ID_OK) 36 | except CancelException: 37 | pass 38 | except Exception: 39 | wx.CallAfter(self.on_fail) 40 | def on_fail(self): 41 | dialog = wx.MessageDialog(self, 'Failed to download updates. Nothing will be installed at this time.', 'Update Failed', wx.OK|wx.ICON_ERROR) 42 | dialog.ShowModal() 43 | dialog.Destroy() 44 | self.EndModal(wx.ID_CANCEL) 45 | def update(self, percent): 46 | if self: 47 | self.gauge.SetValue(percent) 48 | def listener(self, blocks, block_size, total_size): 49 | size = blocks * block_size 50 | percent = size * 100 / total_size 51 | if self: 52 | wx.CallAfter(self.update, percent) 53 | else: 54 | raise CancelException 55 | 56 | def get_remote_revision(): 57 | file = None 58 | try: 59 | file = urllib.urlopen(settings.REVISION_URL) 60 | return int(file.read().strip()) 61 | except Exception: 62 | return -1 63 | finally: 64 | if file: 65 | file.close() 66 | 67 | def download_installer(listener): 68 | fd, path = tempfile.mkstemp('.exe') 69 | os.close(fd) 70 | path, headers = urllib.urlretrieve(settings.INSTALLER_URL, path, listener) 71 | return path 72 | 73 | def should_check(): 74 | last_check = settings.UPDATE_TIMESTAMP 75 | now = int(time.time()) 76 | elapsed = now - last_check 77 | return elapsed >= settings.UPDATE_INTERVAL 78 | 79 | def should_update(force): 80 | if not force: 81 | if not should_check(): 82 | return False 83 | now = int(time.time()) 84 | settings.UPDATE_TIMESTAMP = now 85 | local = settings.LOCAL_REVISION 86 | remote = get_remote_revision() 87 | if local < 0 or remote < 0: 88 | return False 89 | return remote > local 90 | 91 | def do_check(controller, force=False): 92 | if should_update(force): 93 | wx.CallAfter(do_ask, controller) 94 | elif force: 95 | wx.CallAfter(do_tell, controller) 96 | 97 | def do_ask(controller): 98 | dialog = wx.MessageDialog(None, 'Feed Notifier software updates are available. Download and install now?', 'Update Feed Notifier?', wx.YES_NO|wx.YES_DEFAULT|wx.ICON_QUESTION) 99 | if dialog.ShowModal() == wx.ID_YES: 100 | do_download(controller) 101 | dialog.Destroy() 102 | 103 | def do_tell(controller): 104 | dialog = wx.MessageDialog(None, 'No software updates are available at this time.', 'No Updates', wx.OK|wx.ICON_INFORMATION) 105 | dialog.ShowModal() 106 | dialog.Destroy() 107 | 108 | def do_download(controller): 109 | dialog = DownloadDialog(None) 110 | dialog.Center() 111 | result = dialog.ShowModal() 112 | path = dialog.path 113 | dialog.Destroy() 114 | if result == wx.ID_OK: 115 | do_install(controller, path) 116 | 117 | def do_install(controller, path): 118 | controller.close() 119 | time.sleep(1) 120 | os.execvp(path, (path, '/sp-', '/silent', '/norestart')) 121 | 122 | def run(controller, force=False): 123 | if force or settings.CHECK_FOR_UPDATES: 124 | util.start_thread(do_check, controller, force) 125 | -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | import wx 2 | import os 3 | import re 4 | import time 5 | import base64 6 | import calendar 7 | import urllib2 8 | import urlparse 9 | import threading 10 | import feedparser 11 | from htmlentitydefs import name2codepoint 12 | from settings import settings 13 | 14 | def set_icon(window): 15 | bundle = wx.IconBundle() 16 | bundle.AddIcon(wx.Icon('icons/16.png', wx.BITMAP_TYPE_PNG)) 17 | bundle.AddIcon(wx.Icon('icons/24.png', wx.BITMAP_TYPE_PNG)) 18 | bundle.AddIcon(wx.Icon('icons/32.png', wx.BITMAP_TYPE_PNG)) 19 | bundle.AddIcon(wx.Icon('icons/48.png', wx.BITMAP_TYPE_PNG)) 20 | bundle.AddIcon(wx.Icon('icons/256.png', wx.BITMAP_TYPE_PNG)) 21 | window.SetIcons(bundle) 22 | 23 | def start_thread(func, *args): 24 | thread = threading.Thread(target=func, args=args) 25 | thread.setDaemon(True) 26 | thread.start() 27 | return thread 28 | 29 | def scale_bitmap(bitmap, width, height, color): 30 | bw, bh = bitmap.GetWidth(), bitmap.GetHeight() 31 | if bw == width and bh == height: 32 | return bitmap 33 | if width < 0: 34 | width = bw 35 | if height < 0: 36 | height = bh 37 | buffer = wx.EmptyBitmap(bw, bh) 38 | dc = wx.MemoryDC(buffer) 39 | dc.SetBackground(wx.Brush(color)) 40 | dc.Clear() 41 | dc.DrawBitmap(bitmap, 0, 0, True) 42 | image = wx.ImageFromBitmap(buffer) 43 | image = image.Scale(width, height, wx.IMAGE_QUALITY_HIGH) 44 | result = wx.BitmapFromImage(image) 45 | return result 46 | 47 | def menu_item(menu, label, func, icon=None, kind=wx.ITEM_NORMAL): 48 | item = wx.MenuItem(menu, -1, label, kind=kind) 49 | if func: 50 | menu.Bind(wx.EVT_MENU, func, id=item.GetId()) 51 | if icon: 52 | item.SetBitmap(wx.Bitmap(icon)) 53 | menu.AppendItem(item) 54 | return item 55 | 56 | def select_choice(choice, data): 57 | for index in range(choice.GetCount()): 58 | if choice.GetClientData(index) == data: 59 | choice.Select(index) 60 | return 61 | choice.Select(wx.NOT_FOUND) 62 | 63 | def get_top_window(window): 64 | result = None 65 | while window: 66 | result = window 67 | window = window.GetParent() 68 | return result 69 | 70 | def get(obj, key, default): 71 | value = obj.get(key, None) 72 | return value or default 73 | 74 | def abspath(path): 75 | path = os.path.abspath(path) 76 | path = 'file:///%s' % path.replace('\\', '/') 77 | return path 78 | 79 | def parse(url, username=None, password=None, etag=None, modified=None): 80 | agent = settings.USER_AGENT 81 | handlers = [get_proxy()] 82 | if username and password: 83 | url = insert_credentials(url, username, password) 84 | return feedparser.parse(url, etag=etag, modified=modified, agent=agent, handlers=handlers) 85 | 86 | def is_valid_feed(data): 87 | entries = get(data, 'entries', []) 88 | title = get(data.feed, 'title', '') 89 | link = get(data.feed, 'link', '') 90 | return entries or title or link 91 | 92 | def insert_credentials(url, username, password): 93 | parts = urlparse.urlsplit(url) 94 | netloc = parts.netloc 95 | if '@' in netloc: 96 | netloc = netloc[netloc.index('@')+1:] 97 | netloc = '%s:%s@%s' % (username, password, netloc) 98 | parts = list(parts) 99 | parts[1] = netloc 100 | return urlparse.urlunsplit(tuple(parts)) 101 | 102 | def encode_password(password): 103 | return base64.b64encode(password) if password else None 104 | 105 | def decode_password(password): 106 | try: 107 | return base64.b64decode(password) if password else None 108 | except Exception: 109 | return None 110 | 111 | def get_proxy(): 112 | if settings.USE_PROXY: 113 | url = decode_password(settings.PROXY_URL) 114 | if url: 115 | # User-configured Proxy 116 | map = { 117 | 'http': url, 118 | 'https': url, 119 | } 120 | proxy = urllib2.ProxyHandler(map) 121 | else: 122 | # Windows-configured Proxy 123 | proxy = urllib2.ProxyHandler() 124 | else: 125 | # No Proxy 126 | proxy = urllib2.ProxyHandler({}) 127 | return proxy 128 | 129 | def find_themes(): 130 | return ['default'] # TODO: more themes! 131 | result = [] 132 | names = os.listdir('themes') 133 | for name in names: 134 | if name.startswith('.'): 135 | continue 136 | path = os.path.join('themes', name) 137 | if os.path.isdir(path): 138 | result.append(name) 139 | return result 140 | 141 | def guess_polling_interval(entries): 142 | if len(entries) < 2: 143 | return settings.DEFAULT_POLLING_INTERVAL 144 | timestamps = [] 145 | for entry in entries: 146 | timestamp = calendar.timegm(get(entry, 'date_parsed', time.gmtime())) 147 | timestamps.append(timestamp) 148 | timestamps.sort() 149 | durations = [b - a for a, b in zip(timestamps, timestamps[1:])] 150 | mean = sum(durations) / len(durations) 151 | choices = [ 152 | 60, 153 | 60*5, 154 | 60*10, 155 | 60*15, 156 | 60*30, 157 | 60*60, 158 | 60*60*2, 159 | 60*60*4, 160 | 60*60*8, 161 | 60*60*12, 162 | 60*60*24, 163 | ] 164 | desired = mean / 2 165 | if desired == 0: 166 | interval = settings.DEFAULT_POLLING_INTERVAL 167 | elif desired < choices[0]: 168 | interval = choices[0] 169 | else: 170 | interval = max(choice for choice in choices if choice <= desired) 171 | return interval 172 | 173 | def time_since(t): 174 | t = int(t) 175 | now = int(time.time()) 176 | seconds = max(now - t, 0) 177 | if seconds == 1: 178 | return '1 second' 179 | if seconds < 60: 180 | return '%d seconds' % seconds 181 | minutes = seconds / 60 182 | if minutes == 1: 183 | return '1 minute' 184 | if minutes < 60: 185 | return '%d minutes' % minutes 186 | hours = minutes / 60 187 | if hours == 1: 188 | return '1 hour' 189 | if hours < 24: 190 | return '%d hours' % hours 191 | days = hours / 24 192 | if days == 1: 193 | return '1 day' 194 | return '%d days' % days 195 | 196 | def split_time(seconds): 197 | if seconds < 60: 198 | return seconds, 0 199 | minutes = seconds / 60 200 | if minutes < 60: 201 | return minutes, 1 202 | hours = minutes / 60 203 | days = hours / 24 204 | if days and hours % 24 == 0: 205 | return days, 3 206 | return hours, 2 207 | 208 | def split_time_str(seconds): 209 | interval, units = split_time(seconds) 210 | strings = ['second', 'minute', 'hour', 'day'] 211 | string = strings[units] 212 | if interval != 1: 213 | string += 's' 214 | return '%d %s' % (interval, string) 215 | 216 | def pretty_name(name): 217 | name = ' '.join(s.title() for s in name.split('_')) 218 | last = '0' 219 | result = '' 220 | for c in name: 221 | if c.isdigit() and not last.isdigit(): 222 | result += ' ' 223 | result += c 224 | last = c 225 | return result 226 | 227 | def replace_entities1(text): 228 | entity = re.compile(r'&#(\d+);') 229 | def func(match): 230 | try: 231 | return unichr(int(match.group(1))) 232 | except Exception: 233 | return match.group(0) 234 | return entity.sub(func, text) 235 | 236 | def replace_entities2(text): 237 | entity = re.compile(r'&([a-zA-Z]+);') 238 | def func(match): 239 | try: 240 | return unichr(name2codepoint[match.group(1)]) 241 | except Exception: 242 | return match.group(0) 243 | return entity.sub(func, text) 244 | 245 | def remove_markup(text): 246 | html = re.compile(r'<[^>]+>') 247 | return html.sub(' ', text) 248 | 249 | def format(text, max_length=400): 250 | previous = '' 251 | while text != previous: 252 | previous = text 253 | text = replace_entities1(text) 254 | text = replace_entities2(text) 255 | text = remove_markup(text) 256 | text = ' '.join(text.split()) 257 | if len(text) > max_length: 258 | text = text[:max_length].strip() 259 | text = text.split()[:-1] 260 | text.append('[...]') 261 | text = ' '.join(text) 262 | return text 263 | -------------------------------------------------------------------------------- /www/download.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Feed Notifier - RSS System Tray Popup Notifications 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 28 | 29 |
30 | 62 |
63 |
64 |

Download the latest release.

65 |

Download Feed Notifier 2.1 (7 MB)

66 |

What's new in v2.1?

67 |
    68 |
  • Better performance due to switch from ActiveX control to pure Python pop-ups.
  • 69 |
  • Software automatically checks for new versions to download and install.
  • 70 |
  • Better cross-platform support, in preparation for a Linux release.
  • 71 |
72 |

What's new in v2.0?

73 |
    74 |
  • Improved look and feel with support for themes.
  • 75 |
  • "Update Feeds" option to check all feeds on demand.
  • 76 |
  • Configurable pop-up size.
  • 77 |
  • Enable/Disable individual feeds without deleting them entirely.
  • 78 |
  • "Run at Startup" option in the installer.
  • 79 |
  • Item age displayed in pop-up, e.g. "5 minutes ago"
  • 80 |
  • Item author displayed in pop-up.
  • 81 |
  • Navigation controls displayed in pop-up, replacing the awkward pop-up stacking in v1.0.
  • 82 |
  • "Auto-play" option can be turned off to prevent popups from disappearing automatically.
  • 83 |
  • Pop-ups can be displayed in the center of the screen.
  • 84 |
  • System tray icon changes when the app is checking feeds.
  • 85 |
  • Automatically computes a good polling interval when adding a new feed.
  • 86 |
  • Shows the favicon for the feed when available.
  • 87 |
  • Number of items shown in pop-up, e.g. "4 of 12"
  • 88 |
  • Ability to edit feeds and configure feed titles and links.
  • 89 |
  • Proxy server support.
  • 90 |
  • Many more user preferences.
  • 91 |
92 |

All versions.

93 |

Feed Notifier 2.1 (7 MB)

94 |

Feed Notifier 2.0 (7 MB)

95 |

Feed Notifier 1.0 (7 MB)

96 |
97 |
98 |
99 |
100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/favicon.ico -------------------------------------------------------------------------------- /www/firefox.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Feed Notifier - RSS System Tray Popup Notifications 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 28 | 29 |
30 | 62 |
63 |
64 |

Adding Feeds Directly From Firefox

65 |

Step 1: Find Something You Like

66 |

Go to a website that you frequently visit to check for updates, even though an app could do it for you. Hopefully it will have an RSS feed. If it does, Firefox will display an RSS icon in the address bar. Click on it.

67 |

Note: Some websites don't include the proper code to make the icon appear in the address bar even though they have an RSS feed. Look around on the webpage itself for an RSS icon.

68 |

69 | 70 |

Step 2: Choose Application

71 |

Once you click on the RSS icon, Firefox will show the contents of the feed. At the top will be some controls to let you register the feed in an external application. From the drop-down menu, select "Choose Application..."

72 |

Note: You only have to do Steps 2 and 3 once!

73 |

74 | 75 |

Step 3: Browse For notifier.exe

76 |

Browse to the location where you installed Feed Notifier. It will probably be:

77 |

C:\Program Files\Feed Notifier\notifier.exe

78 |

79 | 80 |

Step 4: Subscribe Now

81 |

Click the "Subscribe Now" button. Firefox will send the link to Feed Notifier.

82 |

83 | 84 |

Step 5: Click Next

85 |

You should see Feed Notifier pop up this window to add a new feed. If not, look underneath other windows. Click "Next"

86 |

87 | 88 |

Step 6: Click Finish

89 |

Configure the feed properties if you want, or just click "Finish"

90 |

91 |
92 |
93 |
94 |
95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /www/images/contact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/contact.png -------------------------------------------------------------------------------- /www/images/craigslist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/craigslist.png -------------------------------------------------------------------------------- /www/images/edit_feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/edit_feed.png -------------------------------------------------------------------------------- /www/images/feeds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/feeds.png -------------------------------------------------------------------------------- /www/images/firefox1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/firefox1.png -------------------------------------------------------------------------------- /www/images/firefox2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/firefox2.png -------------------------------------------------------------------------------- /www/images/firefox3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/firefox3.png -------------------------------------------------------------------------------- /www/images/firefox4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/firefox4.png -------------------------------------------------------------------------------- /www/images/firefox5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/firefox5.png -------------------------------------------------------------------------------- /www/images/firefox6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/firefox6.png -------------------------------------------------------------------------------- /www/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/icon.png -------------------------------------------------------------------------------- /www/images/lifehacker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/lifehacker.png -------------------------------------------------------------------------------- /www/images/linux-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/linux-desktop.png -------------------------------------------------------------------------------- /www/images/linux-feeds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/linux-feeds.png -------------------------------------------------------------------------------- /www/images/linux-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/linux-options.png -------------------------------------------------------------------------------- /www/images/linux-popups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/linux-popups.png -------------------------------------------------------------------------------- /www/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/logo.png -------------------------------------------------------------------------------- /www/images/options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/options.png -------------------------------------------------------------------------------- /www/images/popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/popup.png -------------------------------------------------------------------------------- /www/images/popups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/popups.png -------------------------------------------------------------------------------- /www/images/sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sidebar.png -------------------------------------------------------------------------------- /www/images/sites/bbc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/bbc.png -------------------------------------------------------------------------------- /www/images/sites/blogspot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/blogspot.png -------------------------------------------------------------------------------- /www/images/sites/cnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/cnet.png -------------------------------------------------------------------------------- /www/images/sites/cnn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/cnn.png -------------------------------------------------------------------------------- /www/images/sites/craigslist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/craigslist.png -------------------------------------------------------------------------------- /www/images/sites/digg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/digg.png -------------------------------------------------------------------------------- /www/images/sites/ebay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/ebay.png -------------------------------------------------------------------------------- /www/images/sites/engadget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/engadget.png -------------------------------------------------------------------------------- /www/images/sites/espn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/espn.png -------------------------------------------------------------------------------- /www/images/sites/filehippo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/filehippo.png -------------------------------------------------------------------------------- /www/images/sites/gmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/gmail.png -------------------------------------------------------------------------------- /www/images/sites/google-blogs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/google-blogs.png -------------------------------------------------------------------------------- /www/images/sites/google-finance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/google-finance.png -------------------------------------------------------------------------------- /www/images/sites/google-groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/google-groups.png -------------------------------------------------------------------------------- /www/images/sites/google-news.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/google-news.png -------------------------------------------------------------------------------- /www/images/sites/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/linkedin.png -------------------------------------------------------------------------------- /www/images/sites/nytimes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/nytimes.png -------------------------------------------------------------------------------- /www/images/sites/reuters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/reuters.png -------------------------------------------------------------------------------- /www/images/sites/slashdot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/slashdot.png -------------------------------------------------------------------------------- /www/images/sites/stackoverflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/stackoverflow.png -------------------------------------------------------------------------------- /www/images/sites/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/twitter.png -------------------------------------------------------------------------------- /www/images/sites/wikipedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/wikipedia.png -------------------------------------------------------------------------------- /www/images/sites/wired.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/wired.png -------------------------------------------------------------------------------- /www/images/sites/wordpress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/wordpress.png -------------------------------------------------------------------------------- /www/images/sites/xkcd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/xkcd.png -------------------------------------------------------------------------------- /www/images/sites/yelp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/yelp.png -------------------------------------------------------------------------------- /www/images/sites/youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/sites/youtube.png -------------------------------------------------------------------------------- /www/images/superuser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/superuser.png -------------------------------------------------------------------------------- /www/images/themes/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/themes/default.png -------------------------------------------------------------------------------- /www/images/themes/minimal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/themes/minimal.png -------------------------------------------------------------------------------- /www/images/themes/simons_quest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/themes/simons_quest.png -------------------------------------------------------------------------------- /www/images/weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/weather.png -------------------------------------------------------------------------------- /www/images/yelp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/images/yelp.png -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Feed Notifier - RSS System Tray Popup Notifications 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 28 | 29 |
30 | 62 |
63 |
64 |

What is Feed Notifier?

65 |

Feed Notifier is a Windows application that resides in the system tray and displays pop-up notifications on your desktop when new items are discovered in subscribed RSS/Atom feeds. By default, the pop-ups look like this...

66 |

67 |

Feed Notifier is for you if you want a news aggregator that focuses on real-time feed notifications and leaves out all the other stuff that comes with most news readers.

68 |

Download the latest release.

69 |

Last updated: 2010-02-17

70 |

Download Feed Notifier 2.1 (7 MB)

71 |

What features does it have?

72 |
    73 |
  • Supports all common RSS and Atom web feed protocols.
  • 74 |
  • Clean look and feel with user selectable themes.
  • 75 |
  • Supports launching from Firefox and other browsers via feed:// protocol.
  • 76 |
  • Supports enabling/disabling individual feeds.
  • 77 |
  • Configurable polling interval for each feed.
  • 78 |
  • Configurable popup duration.
  • 79 |
  • Configurable popup size and position.
  • 80 |
  • Configurable popup transparency.
  • 81 |
  • Popups do not steal keyboard or mouse focus from other applications.
  • 82 |
  • Popups show item age and author.
  • 83 |
  • Navigation controls in popups to view next/previous items.
  • 84 |
  • Deactivates when user is idle to save bandwidth and processing time.
  • 85 |
  • Supports using a proxy server.
  • 86 |
  • Displays favicon for feeds when available.
  • 87 |
88 |

What's new?

89 |

2010-02-17 : Feed Notifier 2.1 released! Feed Notifier 2.1 includes performance enhancements and a fix for occasional popup errors on Windows 7 and Vista. Version 2.1 also includes a software update feature to automatically check for new versions. The default theme was ported to pure Python code instead of HTML rendered by an Internet Explorer ActiveX control. The result is better performance, less memory overhead, no more quirky behavior on Windows 7 and better cross-platform support. A Linux release is probably not too far off (see screenshot), but a Mac release will require tweaking some other cross-platform issues.

90 |

2010-02-05 : Mac and Linux support looks promising. Feed Notifier 2.0 currently uses an Internet Explorer ActiveX control to render the popup windows. This works great but isn't cross platform. Since 2.0 was released, I've been working on porting the application to use WebKit (the wxPython port of it) so that Mac and Linux support can be possible. The new code is working but there are still a few minor quirks to work out. The bottom line is that cross-platform support definitely looks feasible and shouldn't be too far off!

91 |

2010-01-27 : Feed Notifier 2.0 released! Feed Notifier launched one year ago and received lots of user feedback and feature requests. Version 2.0 is the result of integrating all of these requests into an even better application!

92 |

What's on our wishlist?

93 |

Here are some features that we hope to add to Feed Notifier in upcoming releases. Have an idea that's not listed? Let us know.

94 |
    95 |
  • Keyword filters - include/exclude certain keywords on a per-feed basis.
  • 96 |
  • Multi-threaded downloads - check feeds on multiple threads in parallel to read feeds faster.
  • 97 |
  • OPML support - import/export feed information.
  • 98 |
  • Authorization improvements - separate username/password fields when adding a feed.
  • 99 |
  • Quiet mode - instead of popping up new items, change the system tray icon to something that will let the user know new items are available.
  • 100 |
  • Folder structure - to keep feeds better organized and to be able to enable/disable whole groups of feeds with one click.
  • 101 |
  • Theme configuration - user configurable popup colors and fonts.
  • 102 |
  • Better popup positioning - let user put popup anywhere on the screen, not just in predetermined locations.
  • 103 |
104 |

Who did this?

105 |

Michael Fogleman, a full-time software engineer who likes to work on useful applications in his spare time.

106 |

I can be contacted at:

107 |

Technologies

108 |

What Feed Notifier is made of...

109 | 118 |
119 |
120 |
121 |
122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /www/pad.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 3.10 5 | PADGen 3.1.0.41 http://www.padgen.org 6 | Portable Application Description, or PAD for short, is a data set that is used by shareware authors to disseminate information to anyone interested in their software products. To find out more go to http://www.asp-shareware.org/pad 7 | 8 | 9 | Michael Fogleman 10 | 404 Gravel Brook Court 11 | 12 | Cary 13 | NC 14 | 27519 15 | US 16 | http://www.michaelfogleman.com/ 17 | 18 | Michael 19 | Fogleman 20 | michael.fogleman@gmail.com 21 | Michael 22 | Fogleman 23 | michael.fogleman@gmail.com 24 | 25 | 26 | michael.fogleman@gmail.com 27 | michael.fogleman@gmail.com 28 | michael.fogleman@gmail.com 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Feed Notifier 37 | 2.6 38 | 05 39 | 06 40 | 2013 41 | 42 | 43 | 44 | Freeware 45 | Minor Update 46 | Install and Uninstall 47 | Win2000,Win7 x32,Win7 x64,WinServer,WinVista,WinVista x64,WinXP 48 | English 49 | 50 | Internet 51 | Network & Internet::Other 52 | 53 | 54 | 7316767 55 | 7145 56 | 6.98 57 | 58 | 59 | N 60 | 61 | Days 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | rss,atom,feed,aggregator,reader,notifier,notification,system,tray,windows,popup 71 | RSS/Atom System Tray Pop-up Notifications 72 | Feed Notifier displays RSS/Atom pop-up notifications on your desktop. 73 | Feed Notifier is a Windows application that resides in the system tray and displays pop-up notifications on your desktop when new items are discovered in subscribed RSS/Atom feeds. 74 | Feed Notifier is a Windows application that resides in the system tray and displays pop-up notifications on your desktop when new items are discovered in subscribed RSS/Atom feeds. Feed Notifier is for you if you want a news aggregator that focuses on real-time feed notifications and leaves out all the other stuff that comes with most news readers. 75 | Feed Notifier is a Windows application that resides in the system tray and displays pop-up notifications on your desktop when new items are discovered in subscribed RSS/Atom feeds. 76 | 77 | Feed Notifier is for you if you want a news aggregator that focuses on real-time feed notifications and leaves out all the other stuff that comes with most news readers. 78 | 79 | Features include: 80 | 81 | * Support for all common RSS and Atom web feed protocols. 82 | * Clean look and feel with user selectable themes. 83 | * Support for launching from Firefox and other browsers via feed:// protocol. 84 | * Support for enabling/disabling individual feeds. 85 | * Configurable polling interval for each feed. 86 | * Configurable pop-up duration. 87 | * Configurable pop-up size and position. 88 | * Configurable pop-up transparency. 89 | * Pop-ups do not steal keyboard or mouse focus from other applications. 90 | * Pop-ups show item age and author. 91 | * Navigation controls in pop-ups to view next/previous items. 92 | * Deactivates when user is idle to save bandwidth and processing time. 93 | * Support for using a proxy server. 94 | * Displays favicon for feeds when available. 95 | 96 | 97 | 98 | 99 | 100 | http://www.feednotifier.com/ 101 | 102 | http://www.feednotifier.com/images/popup.png 103 | http://www.feednotifier.com/images/icon.png 104 | http://www.feednotifier.com/pad.xml 105 | 106 | 107 | http://www.feednotifier.com/files/feed-notifier-2.6.exe 108 | http://www.michaelfogleman.com/static/files/feed-notifier-2.6.exe 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /www/popup/control_end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/popup/control_end.png -------------------------------------------------------------------------------- /www/popup/control_fastforward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/popup/control_fastforward.png -------------------------------------------------------------------------------- /www/popup/control_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/popup/control_pause.png -------------------------------------------------------------------------------- /www/popup/control_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/popup/control_play.png -------------------------------------------------------------------------------- /www/popup/control_rewind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/popup/control_rewind.png -------------------------------------------------------------------------------- /www/popup/control_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/popup/control_start.png -------------------------------------------------------------------------------- /www/popup/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/popup/cross.png -------------------------------------------------------------------------------- /www/popup/feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/FeedNotifier/786111338790e0031c58b4286a361af3452442b8/www/popup/feed.png -------------------------------------------------------------------------------- /www/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Feed Notifier 8 | 9 | 10 | 105 | 106 | 107 | 108 |
109 |
110 | 126 |
127 | 130 |
131 | C recipe 577019 by Logic Kills (browser hacking, firefox, flashdrive, forensics, IT Security, sqlite). I have recently been looking into IT forensics and have made a few tools that help with that kind of stuff. This program find and grabs the history.sqlite file from the browser. It then exports it to the directory and drive the program is being executed from. It is meant to be installed on a [...] 132 |
133 |
134 | 145 |
146 |
147 | 148 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /www/screenshots.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Feed Notifier - RSS System Tray Popup Notifications 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 28 | 29 |
30 | 62 |
63 |
64 |

Screenshots!

65 |

Feed Notifier has a minimalistic user interface. All you usually see are the pop-ups and the icon in the system tray. However, here are some sample pop-ups and screenshots of the user preferences.

66 |

To see all the different themes for the pop-ups, check out the Themes page.

67 |

Sample Pop-ups

68 |

69 |

70 |

71 |

Feeds

72 |

This tab lists all of the feeds that the application is managing. You can add, edit, delete and disable feeds from this tab. You can also sort the feeds by polling interval, title or URL.

73 |

74 |

Pop-up Options

75 |

This tab allows you to configure how the pop-ups look and behave.

76 |

77 |

Application Options

78 |

This tab allows you to configure various application settings as well as clear its caches.

79 |

80 |
81 |
82 |
83 |
84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /www/style.css: -------------------------------------------------------------------------------- 1 | a img { 2 | border: none; 3 | } 4 | 5 | body { 6 | margin: 15px 15px; 7 | padding: 0; 8 | font-family: Tahoma, Arial, sans-serif; 9 | } 10 | 11 | .clear { 12 | clear: both; 13 | } 14 | 15 | #container { 16 | background: url('images/sidebar.png') repeat-y; 17 | } 18 | 19 | #sidebar { 20 | width: 180px; 21 | float: left; 22 | } 23 | 24 | #logo img { 25 | margin: 20px 25px 10px 25px; 26 | } 27 | 28 | #title h3 { 29 | margin: 0; 30 | margin-left: 15px; 31 | margin-bottom: 4px; 32 | } 33 | 34 | #title a { 35 | color: #000; 36 | text-decoration: none; 37 | } 38 | 39 | #title a:hover { 40 | text-decoration: underline; 41 | } 42 | 43 | #subtitle p { 44 | margin: 0; 45 | margin-left: 15px; 46 | font-size: 9pt; 47 | } 48 | 49 | #menu { 50 | margin: 10px 0; 51 | } 52 | 53 | #menu ul { 54 | list-style: none; 55 | margin: 0 10px; 56 | padding: 10px 10px; 57 | border-top: 3px solid #aaa; 58 | border-bottom: 3px solid #aaa; 59 | } 60 | 61 | #menu li { 62 | margin: 0; 63 | padding: 0; 64 | line-height: 200%; 65 | font-weight: bold; 66 | } 67 | 68 | #menu a { 69 | color: #00f; 70 | text-decoration: none; 71 | } 72 | 73 | #menu a:hover { 74 | text-decoration: underline; 75 | } 76 | 77 | #donate { 78 | margin: 20px; 79 | } 80 | 81 | #links { 82 | text-align: center; 83 | } 84 | 85 | #body { 86 | margin-left: 195px; 87 | max-width: 800px; 88 | border: 1px solid #ccc; 89 | } 90 | 91 | #content { 92 | margin: 15px 25px; 93 | } 94 | 95 | #content p, #content ul { 96 | font-family: Arial; 97 | font-size: 11pt; 98 | line-height: 150%; 99 | } 100 | 101 | #content img { 102 | vertical-align: middle; 103 | padding: 1px; 104 | } 105 | -------------------------------------------------------------------------------- /www/support.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Feed Notifier - RSS System Tray Popup Notifications 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 28 | 29 |
30 | 62 |
63 |
64 |

Support

65 |

Questions, comments or suggestions?

66 |

Like Feed Notifier? Don't like it? Have a feature request? Send an e-mail to:

67 |

How do I use authenticated feeds that require a username and password?

68 |

Authenticated feeds are supported, but not in a very secure way. Improving this is on our to-do list, but the current method is to put your username and password in the feed URL as shown below.

69 |

http://username:password@www.domain.com/feed/file.xml

70 |

Add the bold part to the URL before entering it, of course substituting your own username and password.

71 |

How do I add feeds directly from Firefox or other browsers?

72 |

See the Firefox Integration page for detailed instructions. It's simple, really.

73 |
74 |
75 |
76 |
77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /www/themes.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Feed Notifier - RSS System Tray Popup Notifications 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 28 | 29 |
30 | 62 |
63 |
64 |

Themes!

65 |

Feed Notifier supports themes for its notification pop-ups. There aren't many yet, but we're working on that...

66 |

Default

67 |

68 |

Minimal

69 |

70 |

Simon's Quest

71 |


By Ryan Sturmer

72 |
73 |
74 |
75 |
76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /www/welcome.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Feed Notifier 5 | 6 | 7 | Feed Notifier Welcomes You! 8 | 9 | urn:uuid:8d8451c3-dc10-4192-8e12-976cf1fb39f2 10 | 2010-01-23T00:00:00Z 11 | 12 | Michael Fogleman 13 | 14 | 15 | Thanks for downloading Feed Notifier! 16 | Right-click on the RSS icon in the system tray to add more 17 | feeds and setup other preferences. 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------