Merge pull request #19 from alexbutum/master
Be more Pythonic and Python 3.x compatible
This commit is contained in:
commit
e48521b1ed
|
@ -1,10 +1,10 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import pygtk
|
import pygtk
|
||||||
|
|
||||||
pygtk.require('2.0')
|
pygtk.require('2.0')
|
||||||
import gtk
|
import gtk
|
||||||
|
|
||||||
import requests
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import json
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
|
@ -20,133 +20,149 @@ from appindicator_replacement import get_icon_filename
|
||||||
|
|
||||||
##This is to get --version to work
|
##This is to get --version to work
|
||||||
try:
|
try:
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
__version = pkg_resources.require("hackertray")[0].version
|
|
||||||
except ImportError, e:
|
__version = pkg_resources.require("hackertray")[0].version
|
||||||
__version = "Can't read version number."
|
except ImportError:
|
||||||
|
__version = "Can't read version number."
|
||||||
|
|
||||||
from hackernews import HackerNews
|
from hackernews import HackerNews
|
||||||
|
|
||||||
|
|
||||||
class HackerNewsApp:
|
class HackerNewsApp:
|
||||||
HN_URL_PREFIX="https://news.ycombinator.com/item?id="
|
HN_URL_PREFIX = "https://news.ycombinator.com/item?id="
|
||||||
def __init__(self):
|
|
||||||
#Load the database
|
|
||||||
home = expanduser("~")
|
|
||||||
with open(home+'/.hackertray.json', 'a+') as content_file:
|
|
||||||
content_file.seek(0)
|
|
||||||
content = content_file.read()
|
|
||||||
try:
|
|
||||||
self.db = set(json.loads(content))
|
|
||||||
except:
|
|
||||||
self.db = set()
|
|
||||||
|
|
||||||
# create an indicator applet
|
def __init__(self):
|
||||||
self.ind = appindicator.Indicator ("Hacker Tray", "hacker-tray", appindicator.CATEGORY_APPLICATION_STATUS)
|
#Load the database
|
||||||
self.ind.set_status (appindicator.STATUS_ACTIVE)
|
home = expanduser("~")
|
||||||
self.ind.set_icon(get_icon_filename("hacker-tray.png"))
|
with open(home + '/.hackertray.json', 'a+') as content_file:
|
||||||
|
content_file.seek(0)
|
||||||
|
content = content_file.read()
|
||||||
|
try:
|
||||||
|
self.db = set(json.loads(content))
|
||||||
|
except ValueError:
|
||||||
|
self.db = set()
|
||||||
|
|
||||||
# create a menu
|
# create an indicator applet
|
||||||
self.menu = gtk.Menu()
|
self.ind = appindicator.Indicator("Hacker Tray", "hacker-tray", appindicator.CATEGORY_APPLICATION_STATUS)
|
||||||
|
self.ind.set_status(appindicator.STATUS_ACTIVE)
|
||||||
|
self.ind.set_icon(get_icon_filename("hacker-tray.png"))
|
||||||
|
|
||||||
#The default state is false, and it toggles when you click on it
|
# create a menu
|
||||||
self.commentState = False
|
self.menu = gtk.Menu()
|
||||||
|
|
||||||
# create items for the menu - refresh, quit and a separator
|
|
||||||
menuSeparator = gtk.SeparatorMenuItem()
|
|
||||||
menuSeparator.show()
|
|
||||||
self.menu.append(menuSeparator)
|
|
||||||
|
|
||||||
btnComments = gtk.CheckMenuItem("Show Comments")
|
#The default state is false, and it toggles when you click on it
|
||||||
btnComments.show()
|
self.commentState = False
|
||||||
btnComments.connect("activate", self.toggleComments)
|
|
||||||
self.menu.append(btnComments)
|
|
||||||
|
|
||||||
btnAbout = gtk.MenuItem("About")
|
# create items for the menu - refresh, quit and a separator
|
||||||
btnAbout.show()
|
menuSeparator = gtk.SeparatorMenuItem()
|
||||||
btnAbout.connect("activate", self.showAbout)
|
menuSeparator.show()
|
||||||
self.menu.append(btnAbout)
|
self.menu.append(menuSeparator)
|
||||||
|
|
||||||
btnRefresh = gtk.MenuItem("Refresh")
|
btnComments = gtk.CheckMenuItem("Show Comments")
|
||||||
btnRefresh.show()
|
btnComments.show()
|
||||||
btnRefresh.connect("activate", self.refresh, True) #the last parameter is for not running the timer
|
btnComments.connect("activate", self.toggleComments)
|
||||||
self.menu.append(btnRefresh)
|
self.menu.append(btnComments)
|
||||||
|
|
||||||
btnQuit = gtk.MenuItem("Quit")
|
btnAbout = gtk.MenuItem("About")
|
||||||
btnQuit.show()
|
btnAbout.show()
|
||||||
btnQuit.connect("activate", self.quit)
|
btnAbout.connect("activate", self.showAbout)
|
||||||
self.menu.append(btnQuit)
|
self.menu.append(btnAbout)
|
||||||
|
|
||||||
self.menu.show()
|
btnRefresh = gtk.MenuItem("Refresh")
|
||||||
|
btnRefresh.show()
|
||||||
|
#the last parameter is for not running the timer
|
||||||
|
btnRefresh.connect("activate", self.refresh, True)
|
||||||
|
self.menu.append(btnRefresh)
|
||||||
|
|
||||||
self.ind.set_menu(self.menu)
|
btnQuit = gtk.MenuItem("Quit")
|
||||||
self.refresh()
|
btnQuit.show()
|
||||||
|
btnQuit.connect("activate", self.quit)
|
||||||
|
self.menu.append(btnQuit)
|
||||||
|
|
||||||
'''Whether comments page is opened or not'''
|
self.menu.show()
|
||||||
def toggleComments(self, widget):
|
|
||||||
self.commentState = not self.commentState
|
|
||||||
|
|
||||||
'''Handle the about btn'''
|
self.ind.set_menu(self.menu)
|
||||||
def showAbout(self, widget):
|
self.refresh()
|
||||||
webbrowser.open("https://github.com/captn3m0/hackertray/")
|
|
||||||
|
|
||||||
''' Handler for the quit button'''
|
def toggleComments(self, widget):
|
||||||
#ToDo: Handle keyboard interrupt properly
|
"""Whether comments page is opened or not"""
|
||||||
def quit(self, widget, data=None):
|
self.commentState = not self.commentState
|
||||||
l=list(self.db)
|
|
||||||
home = expanduser("~")
|
|
||||||
#truncate the file
|
|
||||||
with open(home+'/.hackertray.json', 'w+') as file:
|
|
||||||
file.write(json.dumps(l))
|
|
||||||
gtk.main_quit()
|
|
||||||
|
|
||||||
def run(self):
|
def showAbout(self, widget):
|
||||||
signal.signal(signal.SIGINT, self.quit)
|
"""Handle the about btn"""
|
||||||
gtk.main()
|
webbrowser.open("https://github.com/captn3m0/hackertray/")
|
||||||
return 0
|
|
||||||
|
|
||||||
'''Opens the link in the web browser'''
|
#ToDo: Handle keyboard interrupt properly
|
||||||
def open(self, widget, event=None, data=None):
|
def quit(self, widget, data=None):
|
||||||
#We disconnect and reconnect the event in case we have
|
""" Handler for the quit button"""
|
||||||
#to set it to active and we don't want the signal to be processed
|
l = list(self.db)
|
||||||
if(widget.get_active() == False):
|
home = expanduser("~")
|
||||||
widget.disconnect(widget.signal_id)
|
|
||||||
widget.set_active(True)
|
|
||||||
widget.signal_id = widget.connect('activate', self.open)
|
|
||||||
self.db.add(widget.item_id)
|
|
||||||
webbrowser.open(widget.url)
|
|
||||||
if(self.commentState):
|
|
||||||
webbrowser.open(self.HN_URL_PREFIX+widget.hn_id)
|
|
||||||
|
|
||||||
'''Adds an item to the menu'''
|
#truncate the file
|
||||||
def addItem(self, item):
|
with open(home + '/.hackertray.json', 'w+') as file:
|
||||||
if(item['points'] == 0 or item['points'] == None): #This is in the case of YC Job Postings, which we skip
|
file.write(json.dumps(l))
|
||||||
return
|
|
||||||
i = gtk.CheckMenuItem("("+str(item['points']).zfill(3)+"/"+str(item['comments_count']).zfill(3)+") "+item['title'])
|
gtk.main_quit()
|
||||||
i.set_active(item['id'] in self.db)
|
|
||||||
i.url = item['url']
|
def run(self):
|
||||||
i.signal_id = i.connect('activate', self.open)
|
signal.signal(signal.SIGINT, self.quit)
|
||||||
i.hn_id = item['id']
|
gtk.main()
|
||||||
i.item_id = item['id']
|
return 0
|
||||||
self.menu.prepend(i)
|
|
||||||
i.show()
|
def open(self, widget, event=None, data=None):
|
||||||
|
"""Opens the link in the web browser"""
|
||||||
|
#We disconnect and reconnect the event in case we have
|
||||||
|
#to set it to active and we don't want the signal to be processed
|
||||||
|
if not widget.get_active():
|
||||||
|
widget.disconnect(widget.signal_id)
|
||||||
|
widget.set_active(True)
|
||||||
|
widget.signal_id = widget.connect('activate', self.open)
|
||||||
|
|
||||||
|
self.db.add(widget.item_id)
|
||||||
|
webbrowser.open(widget.url)
|
||||||
|
|
||||||
|
if self.commentState:
|
||||||
|
webbrowser.open(self.HN_URL_PREFIX + widget.hn_id)
|
||||||
|
|
||||||
|
def addItem(self, item):
|
||||||
|
"""Adds an item to the menu"""
|
||||||
|
#This is in the case of YC Job Postings, which we skip
|
||||||
|
if item['points'] == 0 or item['points'] is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
i = gtk.CheckMenuItem(
|
||||||
|
"(" + str(item['points']).zfill(3) + "/" + str(item['comments_count']).zfill(3) + ") " + item['title'])
|
||||||
|
|
||||||
|
i.set_active(item['id'] in self.db)
|
||||||
|
i.url = item['url']
|
||||||
|
i.signal_id = i.connect('activate', self.open)
|
||||||
|
i.hn_id = item['id']
|
||||||
|
i.item_id = item['id']
|
||||||
|
self.menu.prepend(i)
|
||||||
|
i.show()
|
||||||
|
|
||||||
|
def refresh(self, widget=None, no_timer=False):
|
||||||
|
"""Refreshes the menu """
|
||||||
|
data = reversed(HackerNews.getHomePage()[0:20])
|
||||||
|
|
||||||
|
#Remove all the current stories
|
||||||
|
for i in self.menu.get_children():
|
||||||
|
if hasattr(i, 'url'):
|
||||||
|
self.menu.remove(i)
|
||||||
|
|
||||||
|
#Add back all the refreshed news
|
||||||
|
for i in data:
|
||||||
|
self.addItem(i)
|
||||||
|
|
||||||
|
#Call every 5 minutes
|
||||||
|
if not no_timer:
|
||||||
|
gtk.timeout_add(10 * 60 * 1000, self.refresh)
|
||||||
|
|
||||||
'''Refreshes the menu '''
|
|
||||||
def refresh(self, widget=None, no_timer=False):
|
|
||||||
data = reversed(HackerNews.getHomePage()[0:20]);
|
|
||||||
#Remove all the current stories
|
|
||||||
for i in self.menu.get_children():
|
|
||||||
if(hasattr(i,'url')):
|
|
||||||
self.menu.remove(i)
|
|
||||||
#Add back all the refreshed news
|
|
||||||
for i in data:
|
|
||||||
self.addItem(i)
|
|
||||||
#Call every 5 minutes
|
|
||||||
if no_timer==False:
|
|
||||||
gtk.timeout_add(10*60*1000, self.refresh)
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description='Hacker News in your System Tray')
|
parser = argparse.ArgumentParser(description='Hacker News in your System Tray')
|
||||||
parser.add_argument('--version', action='version', version=__version)
|
parser.add_argument('--version', action='version', version=__version)
|
||||||
parser.parse_args()
|
parser.parse_args()
|
||||||
indicator = HackerNewsApp()
|
indicator = HackerNewsApp()
|
||||||
indicator.run()
|
indicator.run()
|
||||||
|
|
|
@ -14,7 +14,6 @@ import gobject
|
||||||
|
|
||||||
# We also need os and sys
|
# We also need os and sys
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
|
|
||||||
from pkg_resources import resource_filename
|
from pkg_resources import resource_filename
|
||||||
|
|
||||||
|
@ -22,100 +21,91 @@ from pkg_resources import resource_filename
|
||||||
CATEGORY_APPLICATION_STATUS = 0
|
CATEGORY_APPLICATION_STATUS = 0
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
STATUS_ACTIVE = 0
|
STATUS_ACTIVE = 0
|
||||||
STATUS_ATTENTION = 1
|
STATUS_ATTENTION = 1
|
||||||
|
|
||||||
# Locations to search for the given icon
|
# Locations to search for the given icon
|
||||||
|
|
||||||
|
|
||||||
def get_icon_filename(icon_name):
|
def get_icon_filename(icon_name):
|
||||||
|
# Determine where the icon is
|
||||||
# Determine where the icon is
|
return os.path.abspath(resource_filename('hackertray.data', 'hacker-tray.png'))
|
||||||
return os.path.abspath(resource_filename('hackertray.data', 'hacker-tray.png'))
|
|
||||||
|
|
||||||
# The main class
|
# The main class
|
||||||
class Indicator:
|
class Indicator:
|
||||||
|
# Constructor
|
||||||
# Constructor
|
def __init__(self, unknown, icon, category):
|
||||||
|
# Store the settings
|
||||||
def __init__ (self,unknown,icon,category):
|
self.inactive_icon = get_icon_filename(icon)
|
||||||
|
self.active_icon = "" # Blank until the user calls set_attention_icon
|
||||||
# Store the settings
|
|
||||||
self.inactive_icon = get_icon_filename(icon)
|
# Create the status icon
|
||||||
self.active_icon = "" # Blank until the user calls set_attention_icon
|
self.icon = gtk.StatusIcon()
|
||||||
|
|
||||||
# Create the status icon
|
# Initialize to the default icon
|
||||||
self.icon = gtk.StatusIcon()
|
self.icon.set_from_file(self.inactive_icon)
|
||||||
|
|
||||||
# Initialize to the default icon
|
# Set the rest of the vars
|
||||||
self.icon.set_from_file(self.inactive_icon)
|
self.menu = None # We have no menu yet
|
||||||
|
|
||||||
# Set the rest of the vars
|
def set_menu(self, menu):
|
||||||
self.menu = None # We have no menu yet
|
# Save a copy of the menu
|
||||||
|
self.menu = menu
|
||||||
def set_menu(self,menu):
|
|
||||||
|
# Now attach the icon's signal
|
||||||
# Save a copy of the menu
|
# to the menu so that it becomes displayed
|
||||||
self.menu = menu
|
# whenever the user clicks it
|
||||||
|
self.icon.connect("activate", self.show_menu)
|
||||||
# Now attach the icon's signal
|
|
||||||
# to the menu so that it becomes displayed
|
def set_status(self, status):
|
||||||
# whenever the user clicks it
|
# Status defines whether the active or inactive
|
||||||
self.icon.connect("activate", self.show_menu)
|
# icon should be displayed.
|
||||||
|
if status == STATUS_ACTIVE:
|
||||||
def set_status(self, status):
|
self.icon.set_from_file(self.inactive_icon)
|
||||||
|
else:
|
||||||
# Status defines whether the active or inactive
|
self.icon.set_from_file(self.active_icon)
|
||||||
# icon should be displayed.
|
|
||||||
if status == STATUS_ACTIVE:
|
def set_label(self, label):
|
||||||
self.icon.set_from_file(self.inactive_icon)
|
self.icon.set_title(label)
|
||||||
else:
|
return
|
||||||
self.icon.set_from_file(self.active_icon)
|
|
||||||
|
def set_icon(self, icon):
|
||||||
def set_label(self, label):
|
# Set the new icon
|
||||||
self.icon.set_title(label)
|
self.icon.set_from_file(get_icon_filename(icon))
|
||||||
return
|
|
||||||
|
def set_attention_icon(self, icon):
|
||||||
def set_icon(self, icon):
|
# Set the icon filename as the attention icon
|
||||||
|
self.active_icon = get_icon_filename(icon)
|
||||||
# Set the new icon
|
|
||||||
self.icon.set_from_file(get_icon_filename(icon))
|
def show_menu(self, widget):
|
||||||
|
# Show the menu
|
||||||
def set_attention_icon(self, icon):
|
self.menu.popup(None, None, None, 0, 0)
|
||||||
|
|
||||||
# Set the icon filename as the attention icon
|
# Get the location and size of the window
|
||||||
self.active_icon = get_icon_filename(icon)
|
mouse_rect = self.menu.get_window().get_frame_extents()
|
||||||
|
|
||||||
def show_menu(self, widget):
|
self.x = mouse_rect.x
|
||||||
|
self.y = mouse_rect.y
|
||||||
# Show the menu
|
self.right = self.x + mouse_rect.width
|
||||||
self.menu.popup(None,None,None,0,0)
|
self.bottom = self.y + mouse_rect.height
|
||||||
|
|
||||||
# Get the location and size of the window
|
# Set a timer to poll the menu
|
||||||
mouse_rect = self.menu.get_window().get_frame_extents()
|
self.timer = gobject.timeout_add(100, self.check_mouse)
|
||||||
|
|
||||||
self.x = mouse_rect.x
|
def check_mouse(self):
|
||||||
self.y = mouse_rect.y
|
if not self.menu.get_window().is_visible():
|
||||||
self.right = self.x + mouse_rect.width
|
return
|
||||||
self.bottom = self.y + mouse_rect.height
|
|
||||||
|
# Now check the global mouse coords
|
||||||
# Set a timer to poll the menu
|
root = self.menu.get_screen().get_root_window()
|
||||||
self.timer = gobject.timeout_add(100, self.check_mouse)
|
|
||||||
|
x, y, z = root.get_pointer()
|
||||||
def check_mouse(self):
|
|
||||||
|
if x < self.x or x > self.right or y < self.y or y > self.bottom:
|
||||||
if not self.menu.get_window().is_visible():
|
self.hide_menu()
|
||||||
return
|
else:
|
||||||
|
return True
|
||||||
# Now check the global mouse coords
|
|
||||||
root = self.menu.get_screen().get_root_window()
|
def hide_menu(self):
|
||||||
|
self.menu.popdown()
|
||||||
x,y,z = root.get_pointer()
|
|
||||||
|
|
||||||
if x < self.x or x > self.right or y < self.y or y > self.bottom:
|
|
||||||
self.hide_menu()
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def hide_menu(self):
|
|
||||||
|
|
||||||
self.menu.popdown()
|
|
||||||
|
|
|
@ -2,16 +2,17 @@ import random
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
'https://node-hnapi.herokuapp.com/'
|
'https://node-hnapi.herokuapp.com/'
|
||||||
];
|
]
|
||||||
|
|
||||||
|
|
||||||
class HackerNews:
|
class HackerNews:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getHomePage():
|
def getHomePage():
|
||||||
random.shuffle(urls)
|
random.shuffle(urls)
|
||||||
for i in urls:
|
for i in urls:
|
||||||
r = requests.get(i+"news")
|
r = requests.get(i + "news")
|
||||||
try:
|
try:
|
||||||
return r.json()
|
return r.json()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue;
|
continue
|
|
@ -1,10 +1,12 @@
|
||||||
import unittest
|
import unittest
|
||||||
from hackernews import HackerNews
|
from hackernews import HackerNews
|
||||||
|
|
||||||
|
|
||||||
class HNTest(unittest.TestCase):
|
class HNTest(unittest.TestCase):
|
||||||
def runTest(self):
|
def runTest(self):
|
||||||
data = HackerNews.getHomePage()
|
data = HackerNews.getHomePage()
|
||||||
self.assertTrue(len(data)>0)
|
self.assertTrue(len(data) > 0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
12
setup.py
12
setup.py
|
@ -1,10 +1,12 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
from setuptools import find_packages
|
from setuptools import find_packages
|
||||||
import sys
|
|
||||||
|
|
||||||
requirements = ['requests']
|
requirements = ['requests']
|
||||||
if sys.version_info < (2, 7):
|
if sys.version_info < (2, 7):
|
||||||
requirements.append('argparse')
|
requirements.append('argparse')
|
||||||
|
|
||||||
setup(name='hackertray',
|
setup(name='hackertray',
|
||||||
version='1.9.5',
|
version='1.9.5',
|
||||||
|
@ -17,12 +19,12 @@ setup(name='hackertray',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
package_data={
|
package_data={
|
||||||
'hackertray.data':['hacker-tray.png']
|
'hackertray.data': ['hacker-tray.png']
|
||||||
},
|
},
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'requests>=2.0',
|
'requests>=2.0',
|
||||||
],
|
],
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': ['hackertray = hackertray:main'],
|
'console_scripts': ['hackertray = hackertray:main'],
|
||||||
},
|
},
|
||||||
zip_safe=False)
|
zip_safe=False)
|
||||||
|
|
Loading…
Reference in New Issue