From bae244803c18f394737fdb0fbe42dbd7563fef24 Mon Sep 17 00:00:00 2001 From: sloumdrone Date: Tue, 23 Oct 2018 22:32:26 -0700 Subject: [PATCH 1/8] Started reconfiguring of classes, buggy but semifunctional output is go --- burrow.py | 13 ++ conn.py | 69 +++++----- connect.py | 25 ++++ go.config.json | 2 +- gui.py | 349 +++++++++++++++++++++++++++++++++++++++++++++++++ parser.py | 54 ++++++++ 6 files changed, 475 insertions(+), 37 deletions(-) create mode 100644 burrow.py create mode 100644 connect.py create mode 100644 gui.py create mode 100644 parser.py diff --git a/burrow.py b/burrow.py new file mode 100644 index 0000000..8770d7d --- /dev/null +++ b/burrow.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +################################################ +## Burrow, a gopher client/browser +## - - - - - - - - - - - - - - - - - - - - - - - +## Version 0.2.1 +################################################ + +from gui import GUI + + +if __name__ == '__main__': + app = GUI() + app.root.mainloop() diff --git a/conn.py b/conn.py index 23d19f7..c701b56 100644 --- a/conn.py +++ b/conn.py @@ -28,18 +28,15 @@ class Tunnel: def make_connection(self, resource, host, itemtype, port=70): - endline = '\r\n' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(30.0) - try: - port = int(port) - except: - port = 70 s.connect((host, port)) s.sendall((resource + '\r\n').encode('utf-8')) - r = s.makefile(mode = 'r', errors='ignore') + + req = s.makefile(mode = 'r', errors='ignore') + try: - raw_data = r.read() + raw_data = req.read() except UnicodeDecodeError: raw_data = 'iError decoding server response :(\tfalse\tnull.host\t1' @@ -62,39 +59,39 @@ class Tunnel: - def gopher_to_text(self, message): - message = message.split('\n') - message = [x.split('\t') for x in message] - message = [{'type': x[0][0], 'description': x[0][1:], 'resource': x[1], 'host': x[2], 'port': x[3]} for x in message if len(x) >= 4] - return message + # def gopher_to_text(self, message): + # message = message.split('\n') + # message = [x.split('\t') for x in message] + # message = [{'type': x[0][0], 'description': x[0][1:], 'resource': x[1], 'host': x[2], 'port': x[3]} for x in message if len(x) >= 4] + # return message - def parse_url(self, url, execute=True): - regex = r'^(?P(?:gopher:\/\/)?)?(?P[\w\.\d]+)(?P(?::\d+)?)?(?P(?:\/\d)?)?(?P(?:\/[\w\/\d\-?.]*)?)?$' - match = re.match(regex, url) - protocol = match.group('protocol') - itemtype = match.group('type') - host = match.group('host') - port = match.group('port') - resource = match.group('resource') + # def parse_url(self, url, execute=True): + # regex = r'^(?P(?:gopher:\/\/)?)?(?P[\w\.\d]+)(?P(?::\d+)?)?(?P(?:\/\d)?)?(?P(?:\/[\w\/\d\-?.]*)?)?$' + # match = re.match(regex, url) + # protocol = match.group('protocol') + # itemtype = match.group('type') + # host = match.group('host') + # port = match.group('port') + # resource = match.group('resource') - if protocol != 'gopher://' and protocol: - return False - if itemtype and not self.types[itemtype[1]]: - return False - elif not itemtype: - itemtype = '/1' - if not host: - return False - if not resource: - resource = '/' - if port: - port = port[1:] + # if protocol != 'gopher://' and protocol: + # return False + # if itemtype and not self.types[itemtype[1]]: + # return False + # elif not itemtype: + # itemtype = '/1' + # if not host: + # return False + # if not resource: + # resource = '/' + # if port: + # port = port[1:] - if execute: - self.make_connection(resource, host, itemtype, port) - else: - return {'host': host, 'resource': resource, 'type': itemtype} + # if execute: + # self.make_connection(resource, host, itemtype, port) + # else: + # return {'host': host, 'resource': resource, 'type': itemtype} if __name__ == '__main__': diff --git a/connect.py b/connect.py new file mode 100644 index 0000000..bafe28a --- /dev/null +++ b/connect.py @@ -0,0 +1,25 @@ +import socket + +class connect: + def __init__(self): + self.raw_response = None + self.filetype = None + + def request(self, resource, host, itemtype, port=70): + #connects to server and returns list with response type and body + socket_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + socket_conn.settimeout(20.0) + socket_conn.connect((host, port)) + socket_conn.sendall((resource + '\r\n').encode('utf-8')) + + response = socket_conn.makefile(mode = 'r', errors = 'ignore') + try: + self.raw_response = response.read() + self.filetype = itemtype + except UnicodeDecodeError: + self.raw_response = '3Error decoding server response\tfalse\tnull.host\t1' + self.filetype = '3' + + socket_conn.close() + + return {'type': self.filetype, 'body': self.raw_response} diff --git a/go.config.json b/go.config.json index 879e5ab..89f1b31 100644 --- a/go.config.json +++ b/go.config.json @@ -1 +1 @@ -{"favorites": [], "last_viewed": "gopher://gopher.club:70/1/phlogs/"} \ No newline at end of file +{"favorites": [], "last_viewed": "gopher://gopher.floodgap.com:70/1/"} \ No newline at end of file diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..5648cde --- /dev/null +++ b/gui.py @@ -0,0 +1,349 @@ +#!/usr/bin/env python3 + +import tkinter as tk +from connect import connect as conn +from parser import parser +import time +import sys +import json +import os.path + +class GUI: + def __init__(self): + self.history = [] + self.history_location = -1 + self.message_bar_content = '' + self.config = None + self.read_config() + self.conn = conn() + self.parser = parser() + + #colors + self.FG = 'black' + self.BG = 'white' + self.LINK = 'blue' + self.ACTIVELINK = 'red' + self.HLB = '#e37800' + self.HLF = 'white' + self.STATUS = 'silver' + self.ERROR = 'red' + + #create and configure root window + self.root = tk.Tk(className='Digger') + self.root.title('Digger') + self.root.geometry("1200x800") + self.add_assets() + + #main frame objects + self.top_bar = tk.Frame(self.root, padx=10, height=70, relief=tk.FLAT, bd=2) + self.body = tk.Frame(self.root, relief=tk.RIDGE, bd=2) + self.status_bar = tk.Frame(self.root, height="20", bg=self.STATUS, takefocus=0) + + #top bar objects + self.btn_back = tk.Button(self.top_bar, image=self.img_back, bd=0, highlightthickness=0, takefocus=1) + self.btn_forward = tk.Button(self.top_bar,image=self.img_forward, bd=0, highlightthickness=0) + self.btn_favorite = tk.Button(self.top_bar,image=self.img_favorite, bd=0, highlightthickness=0) + self.btn_home = tk.Button(self.top_bar, image=self.img_home, bd=0, highlightthickness=0) + self.entry_url = tk.Entry(self.top_bar, selectbackground=self.HLB, selectforeground=self.HLF, highlightcolor=self.HLB) + self.btn_menu = tk.Button(self.top_bar, image=self.img_menu, bd=0, highlightthickness=0) + + #body objects + self.scroll_bar = tk.Scrollbar(self.body) + self.site_display = tk.Text(self.body, bg=self.BG, foreground=self.FG, padx=20, pady=20, wrap=tk.WORD, state=tk.DISABLED, spacing2=5, spacing1=5, yscrollcommand=self.scroll_bar.set) + self.scroll_bar.config(command=self.site_display.yview, width=20, relief=tk.RIDGE) + self.site_display.tag_configure('linkcolor', foreground=self.LINK, spacing1=5) + self.site_display.tag_configure('type_tag', background=self.FG, foreground=self.BG, spacing2=0, spacing1=0) + self.site_display.tag_configure('error_text', foreground=self.ERROR, spacing1=5, spacing2=5, spacing3=5) + + #status bar objects + self.status_info = tk.Label(self.status_bar, textvariable=self.message_bar_content, bg=self.STATUS, takefocus=0) + + self.pack_geometry() + self.add_status_titles() + self.add_event_listeners() + + #load the home screen + self.load_home_screen() + + #-----------Start GUI configuration----------------------- + + def add_event_listeners(self): + buttons = [ + self.btn_back, + self.btn_forward, + self.btn_favorite, + self.btn_home, + self.btn_menu + ] + + for x in buttons: + x.bind('', self.update_status) + x.bind('', self.clear_status) + self.entry_url.bind('', self.execute_address) + self.btn_back.bind('', self.go_back) + self.btn_forward.bind('', self.go_forward) + self.btn_home.bind('', self.load_home_screen) + self.site_display.bind("", lambda event: self.site_display.yview_scroll(-1, 'units')) + self.site_display.bind("", lambda event: self.site_display.yview_scroll(1, 'units')) + self.site_display.bind("", lambda event: self.site_display.focus_set()) + self.entry_url.bind("", lambda event: self.entry_url.focus_set()) + self.root.protocol('WM_DELETE_WINDOW', self.close_window) + + + def pack_geometry(self): + self.top_bar.pack(expand=False,fill=tk.BOTH,side=tk.TOP,anchor=tk.NW) + self.top_bar.pack_propagate(False) + self.body.pack(expand=True,fill=tk.BOTH,side=tk.TOP) + self.status_bar.pack(expand=False,fill=tk.X,side=tk.TOP,anchor=tk.SW) + self.btn_back.pack(side=tk.LEFT, padx=(0,20)) + self.btn_forward.pack(side=tk.LEFT, padx=(0,20)) + self.btn_home.pack(side=tk.LEFT, padx=(0,20)) + self.entry_url.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=10, ipadx=10) + self.btn_favorite.pack(side=tk.LEFT, padx=(10,10)) + self.btn_menu.pack(side=tk.LEFT) + self.scroll_bar.pack(side=tk.RIGHT,fill=tk.Y) + self.site_display.pack(expand=True, side=tk.TOP, fill=tk.BOTH) + self.status_info.pack(side=tk.LEFT) + + + def add_status_titles(self): + self.btn_back.pop_title = 'Back' + self.btn_forward.pop_title = 'Forward' + self.btn_favorite.pop_title = 'Favorite' + self.btn_home.pop_title = 'Home' + self.btn_menu.pop_title = 'Menu' + + + def add_assets(self): + self.img_back = tk.PhotoImage(file='./btn_back.png') + self.img_forward = tk.PhotoImage(file='./btn_forward.png') + self.img_favorite = tk.PhotoImage(file='./btn_refresh.png') + self.img_menu = tk.PhotoImage(file='./btn_menu.png') + self.img_home = tk.PhotoImage(file='./btn_home.png') + self.img_menu = tk.PhotoImage(file='./btn_menu.png') + self.message_bar_content = tk.StringVar() + self.message_bar_content.set('Ready.') + + + # ------------Start navigation methods---------------------------- + + + def execute_address(self, event=False, btn_url=False, history=True): + url = btn_url if btn_url else self.entry_url.get() + if url == 'home': + self.load_home_screen() + return True + + parsed_url = self.parser.parse_url(url) + + if not parsed_url: + # To do: build errors class to handle displaying errors + # return errors.url_error + return False + + response = self.conn.request(self.parser.resource, self.parser.host, self.parser.filetype, self.parser.port) + + if not response: + # To do: build errors class to handle displaying errors + # return errors.connection_error_NUMBER + return False + + if history: + self.history = self.history[:self.history_location+1] + self.history.append(url) + self.history_location = len(self.history) - 1 + + # Get the data to the screen + self.site_display.focus_set() + self.config["last_viewed"] = url + + self.send_to_screen(self.conn.raw_response, self.conn.filetype) + return True + + + def gotolink(self, event, href, tag_name): + element = event.widget + element.tag_config(tag_name, background=self.ACTIVELINK) + element.update_idletasks() # make sure change is visible + time.sleep(.5) # optional delay to show changed text + self.entry_url.delete(0,tk.END) + self.entry_url.insert(tk.END,href) + success = self.execute_address() + element.tag_config(tag_name, background=self.BG) # restore tag text style + element.update_idletasks() + + + def load_home_screen(self,event=None): + print('Loading home') + with open('./home.gopher','r') as f: + data = f.read() + self.entry_url.delete(0, tk.END) + self.entry_url.insert(tk.END, 'home') + self.send_to_screen(data, '1') + + + def go_back(self, event): + if len(self.history) <= 1 and self.history_location <= 0: + return False + + self.history_location -= 1 + href = self.history[self.history_location] + self.populate_url_bar(href) + self.execute_address(False, href, False) + + + def go_forward(self, event): + if len(self.history) <= 1 and self.history_location >= len(self.history) - 1: + return False + + self.history_location += 1 + href = self.history[self.history_location] + self.populate_url_bar(href) + self.execute_address(False, href, False) + + + #-------------Start view methods---------------- + + + def load_favorites(self): + header = 'i#############\tfalse\tnull.host\t1\r\ni manually edit in go.config.json\tfalse\tnull.host\t1\r\n or add using the favorites button\tfalse\tnull.host\t1\r\ni\tfalse\tnull.host\t1\r\n' + #soon add code to load in favorites here + self.send_to_screen(data=header, clear=False) + + def show_menu(self, data, clear=True): + if not data: + #error handling will go here + return False + + types = { + '0': '( TEXT )', + '1': '( MENU )', + '3': '( ERROR)', + '7': '( INTR )', + '9': '( BIN )', + 'g': '( GIF )', + 'I': '( IMG )', + 'h': '( HTML )', + 'i': '( INFO )', + 's': '( SOUND)' + } + + self.site_display.config(state=tk.NORMAL) + + if clear: + self.site_display.delete(1.0, tk.END) + print('---------------') + print(data) + for x in data[1:]: + print(x) + if x['type'] == 'i': + self.site_display.insert(tk.END,' \t\t{}\n'.format(x['description'])) + elif x['type'] == '3': + self.site_display.insert(tk.END,' \t\t{}\n'.format(x['description'])) + elif x['type'] in types: + link_count = 0 + + # adapted from: + # https://stackoverflow.com/questions/27760561/tkinter-and-hyperlinks + if x['port'] and x['port'][0] != ':': + x['port'] = ':{}'.format(x['port']) + + link = 'gopher://{}{}/{}{}'.format(x['host'], x['port'], x['type'], x['resource']) + tag_name = 'link{}'.format(link_count) + callback = (lambda event, href=link, tag_name=tag_name: self.gotolink(event, href, tag_name)) + hover = (lambda event, href=link, tag_name=tag_name: self.hoverlink(event, href, tag_name)) + clear = (lambda event, tag_name=tag_name: self.clear_status(event, tag_name)) + self.site_display.tag_bind(tag_name, "", callback) + self.site_display.tag_bind(tag_name, "", hover) + self.site_display.tag_bind(tag_name, '', clear) + self.site_display.insert(tk.END, types[x['type']], ('type_tag',)) + self.site_display.insert(tk.END,'\t\t') + self.site_display.insert(tk.END, x['description'], (tag_name,'linkcolor')) + self.site_display.insert(tk.END, '\n') + link_count += 1 + + self.site_display.config(state=tk.DISABLED) + + return True + + + def show_text(self, data): + print('Showing text') + self.site_display.config(state=tk.NORMAL) + self.site_display.delete(1.0, tk.END) + self.site_display.insert(tk.END, data) + self.site_display.config(state=tk.DISABLED) + + + def send_to_screen(self, data, itemtype='1', clear=True): + if itemtype == '0': + self.show_text(data) + elif itemtype in ['1','3']: + print('got to menu call') + data = self.parser.parse_menu(data) + self.show_menu(data, clear) + + + def update_status(self, event, href=False): + if href: + self.message_bar_content.set(href) + else: + self.message_bar_content.set(event.widget.pop_title) + + + def clear_status(self, event, tag_name=False): + if tag_name: + e = event.widget + e.tag_config(tag_name, underline=0) + self.site_display.config(cursor='xterm') + e.update_idletasks() + self.message_bar_content.set('') + + + def populate_url_bar(self, url): + self.entry_url.delete(0, tk.END) + self.entry_url.insert(tk.END, url) + + + def hoverlink(self, event, href, tag_name): + self.update_status(event, href) + e = event.widget + e.tag_config(tag_name, underline=1) + self.site_display.config(cursor="arrow") + e.update_idletasks() + + + #--------Start file handling methods------------ + + + def read_config(self, url='./go.config.json'): + if not os.path.isfile(url): + self.create_config() + with open('./go.config.json', 'r') as f: + config = f.read() + config = json.loads(config) + self.config = config + + + def write_config(self, config, url='./go.config.json'): + with open(url, 'w') as f: + data = json.dumps(config) + f.write(data) + + + def create_config(self): + config = {"favorites": [],"last_viewed": None} + self.write_config(config) + + + def close_window(self): + self.write_config(self.config) + self.root.destroy() + + + +if __name__ == '__main__': + app = GUI() + app.root.mainloop() + + diff --git a/parser.py b/parser.py new file mode 100644 index 0000000..5251883 --- /dev/null +++ b/parser.py @@ -0,0 +1,54 @@ +import re + +# Handles parsing gopher data: +# URLs, Menus + +class parser: + + def __init__(self): + self.host = None + self.resource = None + self.filetype = None + self.protocol = None + self.port = None + self.menu = None + + def parse_url(self, url): + # Take in a URL and output a dict of the url parts + + regex = r'^(?P(?:gopher:\/\/)?)?(?P[\w\.\d]+)(?P(?::\d+)?)?(?P(?:\/\d)?)?(?P(?:\/[\w\/\d\-?.]*)?)?$' + + match = re.match(regex, url) + protocol = match.group('protocol') + itemtype = match.group('type') + host = match.group('host') + port = match.group('port') + resource = match.group('resource') + + if not host: + return False + + if not resource: + resource = '/' + + + self.filetype = itemtype[len(itemtype) - 1] if itemtype else '1' + self.protocol = protocol if protocol else 'gopher://' + self.port = int(port[1:]) if port else 70 + self.host = host + self.resource = resource + + return {'host': self.host, 'resource': self.resource, 'type': self.filetype, 'protocol': self.protocol, 'port': self.port} + + + def parse_menu(self, text): + # Take in text from connection and output a list + # w/ objects representing each menu item + + message_list = text.split('\n') + message_list = [x.split('\t') for x in message_list] + message_list = [{'type': x[0][0], 'description': x[0][1:], 'resource': x[1], 'host': x[2], 'port': x[3]} for x in message_list if len(x) >= 4] + self.menu = message_list + + return message_list + From 5a8efaecad2a87cda93574e26081806fd6c020a9 Mon Sep 17 00:00:00 2001 From: sloumdrone Date: Wed, 24 Oct 2018 08:10:18 -0700 Subject: [PATCH 2/8] Fixed counter in loop issue --- go.config.json | 2 +- gui.py | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/go.config.json b/go.config.json index 89f1b31..d65dd7d 100644 --- a/go.config.json +++ b/go.config.json @@ -1 +1 @@ -{"favorites": [], "last_viewed": "gopher://gopher.floodgap.com:70/1/"} \ No newline at end of file +{"favorites": [], "last_viewed": "gopher://gopher.floodgap.com:70/0/recent"} \ No newline at end of file diff --git a/gui.py b/gui.py index 5648cde..d9dd7f3 100644 --- a/gui.py +++ b/gui.py @@ -174,7 +174,6 @@ class GUI: def load_home_screen(self,event=None): - print('Loading home') with open('./home.gopher','r') as f: data = f.read() self.entry_url.delete(0, tk.END) @@ -232,16 +231,15 @@ class GUI: if clear: self.site_display.delete(1.0, tk.END) - print('---------------') - print(data) + + link_count = 0 + for x in data[1:]: - print(x) if x['type'] == 'i': self.site_display.insert(tk.END,' \t\t{}\n'.format(x['description'])) elif x['type'] == '3': self.site_display.insert(tk.END,' \t\t{}\n'.format(x['description'])) elif x['type'] in types: - link_count = 0 # adapted from: # https://stackoverflow.com/questions/27760561/tkinter-and-hyperlinks @@ -268,7 +266,6 @@ class GUI: def show_text(self, data): - print('Showing text') self.site_display.config(state=tk.NORMAL) self.site_display.delete(1.0, tk.END) self.site_display.insert(tk.END, data) @@ -279,7 +276,6 @@ class GUI: if itemtype == '0': self.show_text(data) elif itemtype in ['1','3']: - print('got to menu call') data = self.parser.parse_menu(data) self.show_menu(data, clear) From 967233a1365a610925fd425130384d0c095a4b14 Mon Sep 17 00:00:00 2001 From: sloumdrone Date: Wed, 24 Oct 2018 19:53:42 -0700 Subject: [PATCH 3/8] Got back and forward functionality working properly --- conn.py | 103 ----------------- digger.py | 300 ------------------------------------------------- go.config.json | 2 +- gui.py | 16 ++- 4 files changed, 14 insertions(+), 407 deletions(-) delete mode 100644 conn.py delete mode 100755 digger.py diff --git a/conn.py b/conn.py deleted file mode 100644 index c701b56..0000000 --- a/conn.py +++ /dev/null @@ -1,103 +0,0 @@ -import socket -import sys -import re - -class Tunnel: - def __init__(self): - self.raw_request = None - self.text_output = None - self.types = { - '0': '(TXT)', - '1': '(MENU)', - '2': None, - '3': 'Error code', - '4': None, - '5': None, - '6': None, - '7': '(INTER)', - '8': '(TLNET)', - '9': '(BIN)', - '+': None, - 'g': '(GIF)', - 'I': '(IMG)', - 't': None, - 'h': '(HTML)', - 'i': '(INFO)', - 's': '(SOUND)' - } - - - def make_connection(self, resource, host, itemtype, port=70): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(30.0) - s.connect((host, port)) - s.sendall((resource + '\r\n').encode('utf-8')) - - req = s.makefile(mode = 'r', errors='ignore') - - try: - raw_data = req.read() - except UnicodeDecodeError: - raw_data = 'iError decoding server response :(\tfalse\tnull.host\t1' - - try: - data = raw_data.decode('utf-8','ignore') - except: - data = raw_data - - self.raw_request = data - - if itemtype[1] == '1': - #handle menus - self.text_output = self.gopher_to_text(self.raw_request) - elif itemtype[1] == '0': - #handle text files - self.text_output = [self.raw_request] - self.text_output.insert(0,itemtype[1]) - s.close() - return self.text_output - - - - # def gopher_to_text(self, message): - # message = message.split('\n') - # message = [x.split('\t') for x in message] - # message = [{'type': x[0][0], 'description': x[0][1:], 'resource': x[1], 'host': x[2], 'port': x[3]} for x in message if len(x) >= 4] - # return message - - - # def parse_url(self, url, execute=True): - # regex = r'^(?P(?:gopher:\/\/)?)?(?P[\w\.\d]+)(?P(?::\d+)?)?(?P(?:\/\d)?)?(?P(?:\/[\w\/\d\-?.]*)?)?$' - # match = re.match(regex, url) - # protocol = match.group('protocol') - # itemtype = match.group('type') - # host = match.group('host') - # port = match.group('port') - # resource = match.group('resource') - - # if protocol != 'gopher://' and protocol: - # return False - # if itemtype and not self.types[itemtype[1]]: - # return False - # elif not itemtype: - # itemtype = '/1' - # if not host: - # return False - # if not resource: - # resource = '/' - # if port: - # port = port[1:] - - # if execute: - # self.make_connection(resource, host, itemtype, port) - # else: - # return {'host': host, 'resource': resource, 'type': itemtype} - - -if __name__ == '__main__': - inp = sys.argv[1:] - if len(inp) >= 2: - test = Tunnel() - test.make_connection(inp[1],inp[0],'70') - else: - print('Incorrect request') diff --git a/digger.py b/digger.py deleted file mode 100755 index 4421289..0000000 --- a/digger.py +++ /dev/null @@ -1,300 +0,0 @@ -#!/usr/bin/env python3 - -import tkinter as tk -from conn import Tunnel as go -import time -import sys -import json -import os.path - -class GUI: - def __init__(self): - self.history = [] - self.history_location = -1 - self.message_bar_content = '' - self.config = None - self.read_config() - - #colors - self.FG = 'black' - self.BG = 'white' - self.LINK = 'blue' - self.ACTIVELINK = 'red' - self.HLB = '#e37800' - self.HLF = 'white' - self.STATUS = 'silver' - self.ERROR = 'red' - - #create and configure root window - self.root = tk.Tk(className='Digger') - self.root.title('Digger') - self.root.geometry("1200x800") - self.add_assets() - - #main frame objects - self.top_bar = tk.Frame(self.root, padx=10, height=70, relief=tk.FLAT, bd=2) - self.body = tk.Frame(self.root, relief=tk.RIDGE, bd=2) - self.status_bar = tk.Frame(self.root, height="20", bg=self.STATUS, takefocus=0) - - #top bar objects - self.btn_back = tk.Button(self.top_bar, image=self.img_back, bd=0, highlightthickness=0, takefocus=1) - self.btn_forward = tk.Button(self.top_bar,image=self.img_forward, bd=0, highlightthickness=0) - self.btn_favorite = tk.Button(self.top_bar,image=self.img_favorite, bd=0, highlightthickness=0) - self.btn_home = tk.Button(self.top_bar, image=self.img_home, bd=0, highlightthickness=0) - self.entry_url = tk.Entry(self.top_bar, selectbackground=self.HLB, selectforeground=self.HLF, highlightcolor=self.HLB) - self.btn_menu = tk.Button(self.top_bar, image=self.img_menu, bd=0, highlightthickness=0) - - #body objects - self.scroll_bar = tk.Scrollbar(self.body) - self.site_display = tk.Text(self.body, bg=self.BG, foreground=self.FG, padx=20, pady=20, wrap=tk.WORD, state=tk.DISABLED, spacing2=5, spacing1=5, yscrollcommand=self.scroll_bar.set) - self.scroll_bar.config(command=self.site_display.yview, width=20, relief=tk.RIDGE) - self.site_display.tag_configure('linkcolor', foreground=self.LINK, spacing1=5) - self.site_display.tag_configure('type_tag', background=self.FG, foreground=self.BG, spacing2=0, spacing1=0) - self.site_display.tag_configure('error_text', foreground=self.ERROR, spacing1=5, spacing2=5, spacing3=5) - - #status bar objects - self.status_info = tk.Label(self.status_bar, textvariable=self.message_bar_content, bg=self.STATUS, takefocus=0) - - self.pack_geometry() - self.add_status_titles() - self.add_event_listeners() - - #load the home screen - self.load_home_screen() - - #--------------------------------------------------- - - def add_event_listeners(self): - buttons = [ - self.btn_back, - self.btn_forward, - self.btn_favorite, - self.btn_home, - self.btn_menu - ] - - for x in buttons: - x.bind('', self.update_status) - x.bind('', self.clear_status) - self.entry_url.bind('', self.execute_address) - self.btn_back.bind('', self.go_back) - self.btn_forward.bind('', self.go_forward) - self.btn_home.bind('', self.load_home_screen) - self.site_display.bind("", lambda event: self.site_display.yview_scroll(-1, 'units')) - self.site_display.bind("", lambda event: self.site_display.yview_scroll(1, 'units')) - self.site_display.bind("", lambda event: self.site_display.focus_set()) - self.entry_url.bind("", lambda event: self.entry_url.focus_set()) - self.root.protocol('WM_DELETE_WINDOW', self.close_window) - - - def pack_geometry(self): - self.top_bar.pack(expand=False,fill=tk.BOTH,side=tk.TOP,anchor=tk.NW) - self.top_bar.pack_propagate(False) - self.body.pack(expand=True,fill=tk.BOTH,side=tk.TOP) - self.status_bar.pack(expand=False,fill=tk.X,side=tk.TOP,anchor=tk.SW) - self.btn_back.pack(side=tk.LEFT, padx=(0,20)) - self.btn_forward.pack(side=tk.LEFT, padx=(0,20)) - self.btn_home.pack(side=tk.LEFT, padx=(0,20)) - self.entry_url.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=10, ipadx=10) - self.btn_favorite.pack(side=tk.LEFT, padx=(10,10)) - self.btn_menu.pack(side=tk.LEFT) - self.scroll_bar.pack(side=tk.RIGHT,fill=tk.Y) - self.site_display.pack(expand=True, side=tk.TOP, fill=tk.BOTH) - self.status_info.pack(side=tk.LEFT) - - - def add_status_titles(self): - self.btn_back.pop_title = 'Back' - self.btn_forward.pop_title = 'Forward' - self.btn_favorite.pop_title = 'Favorite' - self.btn_home.pop_title = 'Home' - self.btn_menu.pop_title = 'Menu' - - - def add_assets(self): - self.img_back = tk.PhotoImage(file='./btn_back.png') - self.img_forward = tk.PhotoImage(file='./btn_forward.png') - self.img_favorite = tk.PhotoImage(file='./btn_refresh.png') - self.img_menu = tk.PhotoImage(file='./btn_menu.png') - self.img_home = tk.PhotoImage(file='./btn_home.png') - self.img_menu = tk.PhotoImage(file='./btn_menu.png') - self.message_bar_content = tk.StringVar() - self.message_bar_content.set('Ready.') - - - def execute_address(self, event, btn_url=False): - if btn_url and btn_url != -1: - url = btn_url - elif btn_url and btn_url == -1: - return False - else: - url = self.entry_url.get() - if url == 'home': - self.load_home_screen() - return True - self.history = self.history[:self.history_location+1] - self.history.append(url) - self.history_location = len(self.history) - 1 - self.site_display.focus_set() - request = go() - request.parse_url(url) - self.send_to_screen(request.text_output) - self.config["last_viewed"] = url - return True - - - def gotolink(self, event, href, tag_name): - res = event.widget - res.tag_config(tag_name, background=self.ACTIVELINK) # change tag text style - res.update_idletasks() # make sure change is visible - time.sleep(.5) # optional delay to show changed text - self.entry_url.delete(0,tk.END) - self.entry_url.insert(tk.END,href) - self.execute_address(0) #the zero is meaningless, but the function expects a param - res.tag_config(tag_name, background=self.BG) # restore tag text style - res.update_idletasks() - - - def hoverlink(self, event, href, tag_name): - self.update_status(event, href) - e = event.widget - e.tag_config(tag_name, underline=1) - self.site_display.config(cursor="arrow") - e.update_idletasks() - - def load_home_screen(self,event=False): - with open('./home.gopher','r') as f: - data = f.read() - parser = go() - data = parser.gopher_to_text(data) - data.insert(0,'1') - self.entry_url.delete(0, tk.END) - self.entry_url.insert(tk.END, 'home') - self.send_to_screen(data, True) - - def load_favorites(self): - header = 'i#############\tfalse\tnull.host\t1\r\ni manually edit in go.config.json\tfalse\tnull.host\t1\r\n or add using the favorites button\tfalse\tnull.host\t1\r\ni\tfalse\tnull.host\t1\r\n' - #soon add code to load in favorites here - self.send_to_screen(header, False) - - - def send_to_screen(self, data, clear=True): - link_count = 0 - self.site_display.config(state=tk.NORMAL) - if clear: - self.site_display.delete(1.0, tk.END) - types = { - '0': '( TEXT )', - '1': '( MENU )', - '2': None, - '3': '( ERROR)', - '4': None, - '5': None, - '6': None, - '7': '( INTR )', - '8': '(TELNET)', - '9': '( BIN )', - '+': None, - 'g': '( GIF )', - 'I': '( IMG )', - 't': None, - 'h': '( HTML )', - 'i': '( INFO )', - 's': '( SOUND)' - } - - if data[0] == '0': - self.site_display.insert(tk.END, data[1]) - elif data[0] in ['1','3']: - for x in data[1:]: - if x['type'] == 'i': - self.site_display.insert(tk.END,' \t\t{}\n'.format(x['description'])) - elif x['type'] == '3': - self.site_display.insert(tk.END,' \t\t{}\n'.format(x['description'])) - else: - # adapted from: - # https://stackoverflow.com/questions/27760561/tkinter-and-hyperlinks - if x['port'] and x['port'][0] != ':': - x['port'] = ':{}'.format(x['port']) - - link = 'gopher://{}{}/{}{}'.format(x['host'], x['port'], x['type'], x['resource']) - tag_name = 'link{}'.format(link_count) - callback = (lambda event, href=link, tag_name=tag_name: self.gotolink(event, href, tag_name)) - hover = (lambda event, href=link, tag_name=tag_name: self.hoverlink(event, href, tag_name)) - clear = (lambda event, tag_name=tag_name: self.clear_status(event, tag_name)) - self.site_display.tag_bind(tag_name, "", callback) - self.site_display.tag_bind(tag_name, "", hover) - self.site_display.tag_bind(tag_name, '', clear) - self.site_display.insert(tk.END, types[x['type']], ('type_tag',)) - self.site_display.insert(tk.END,'\t\t') - self.site_display.insert(tk.END, x['description'], (tag_name,'linkcolor')) - self.site_display.insert(tk.END, '\n') - link_count += 1 - self.site_display.config(state=tk.DISABLED) - - - def update_status(self, event, href=False): - if href: - self.message_bar_content.set(href) - else: - self.message_bar_content.set(event.widget.pop_title) - - - def clear_status(self, event, tag_name=False): - if tag_name: - e = event.widget - e.tag_config(tag_name, underline=0) - self.site_display.config(cursor='xterm') - e.update_idletasks() - self.message_bar_content.set('') - - - def go_back(self, event): - if len(self.history) > 1 and self.history_location > 0: - self.history_location -= 1 - href = self.history[self.history_location] - self.entry_url.delete(0, tk.END) - self.entry_url.insert(tk.END, href) - else: - href = -1 - self.execute_address(False, href) - - - def go_forward(self, event): - if len(self.history) > 1 and self.history_location < len(self.history) - 1: - self.history_location += 1 - href = self.history[self.history_location] - self.entry_url.delete(0,tk.END) - self.entry_url.insert(tk.END, href) - else: - href = -1 - self.execute_address(False, href) - - def read_config(self, url='./go.config.json'): - if not os.path.isfile(url): - self.create_config() - with open('./go.config.json', 'r') as f: - config = f.read() - config = json.loads(config) - self.config = config - - def write_config(self, config, url='./go.config.json'): - with open(url, 'w') as f: - data = json.dumps(config) - f.write(data) - - def create_config(self): - config = {"favorites": [],"last_viewed": None} - self.write_config(config) - - def close_window(self): - self.write_config(self.config) - self.root.destroy() - - - -if __name__ == '__main__': - app = GUI() - app.root.mainloop() - - diff --git a/go.config.json b/go.config.json index d65dd7d..89f1b31 100644 --- a/go.config.json +++ b/go.config.json @@ -1 +1 @@ -{"favorites": [], "last_viewed": "gopher://gopher.floodgap.com:70/0/recent"} \ No newline at end of file +{"favorites": [], "last_viewed": "gopher://gopher.floodgap.com:70/1/"} \ No newline at end of file diff --git a/gui.py b/gui.py index d9dd7f3..d518418 100644 --- a/gui.py +++ b/gui.py @@ -131,7 +131,8 @@ class GUI: def execute_address(self, event=False, btn_url=False, history=True): url = btn_url if btn_url else self.entry_url.get() if url == 'home': - self.load_home_screen() + adjust_history = 1 if btn_url else None + self.load_home_screen(adjust_history) return True parsed_url = self.parser.parse_url(url) @@ -161,6 +162,13 @@ class GUI: return True + def add_to_history(self, url): + self.history = self.history[:self.history_location+1] + self.history.append(url) + self.history_location = len(self.history) - 1 + + + def gotolink(self, event, href, tag_name): element = event.widget element.tag_config(tag_name, background=self.ACTIVELINK) @@ -178,11 +186,13 @@ class GUI: data = f.read() self.entry_url.delete(0, tk.END) self.entry_url.insert(tk.END, 'home') + if event is None: + self.add_to_history('home') self.send_to_screen(data, '1') def go_back(self, event): - if len(self.history) <= 1 and self.history_location <= 0: + if len(self.history) <= 1 or self.history_location <= 0: return False self.history_location -= 1 @@ -192,7 +202,7 @@ class GUI: def go_forward(self, event): - if len(self.history) <= 1 and self.history_location >= len(self.history) - 1: + if len(self.history) <= 1 or self.history_location >= len(self.history) - 1: return False self.history_location += 1 From a6802e14c382fae7f92ac72dfd946e9c8908dd6a Mon Sep 17 00:00:00 2001 From: sloumdrone Date: Wed, 24 Oct 2018 20:17:36 -0700 Subject: [PATCH 4/8] Adjusted home page and home button behavior --- gui.py | 12 ++++++------ home.gopher | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/gui.py b/gui.py index d518418..e3c6d80 100644 --- a/gui.py +++ b/gui.py @@ -49,10 +49,10 @@ class GUI: #body objects self.scroll_bar = tk.Scrollbar(self.body) - self.site_display = tk.Text(self.body, bg=self.BG, foreground=self.FG, padx=20, pady=20, wrap=tk.WORD, state=tk.DISABLED, spacing2=5, spacing1=5, yscrollcommand=self.scroll_bar.set) + self.site_display = tk.Text(self.body, bg=self.BG, foreground=self.FG, padx=20, pady=20, wrap=tk.WORD, state=tk.DISABLED, spacing2=2, spacing1=2, spacing3=2, yscrollcommand=self.scroll_bar.set) self.scroll_bar.config(command=self.site_display.yview, width=20, relief=tk.RIDGE) - self.site_display.tag_configure('linkcolor', foreground=self.LINK, spacing1=5) - self.site_display.tag_configure('type_tag', background=self.FG, foreground=self.BG, spacing2=0, spacing1=0) + self.site_display.tag_configure('linkcolor', foreground=self.LINK, spacing1=5, spacing2=5, spacing3=5) + self.site_display.tag_configure('type_tag', background=self.FG, foreground=self.BG, spacing2=1, spacing1=1, spacing3=1) self.site_display.tag_configure('error_text', foreground=self.ERROR, spacing1=5, spacing2=5, spacing3=5) #status bar objects @@ -63,7 +63,7 @@ class GUI: self.add_event_listeners() #load the home screen - self.load_home_screen() + self.load_home_screen(1) #-----------Start GUI configuration----------------------- @@ -131,7 +131,7 @@ class GUI: def execute_address(self, event=False, btn_url=False, history=True): url = btn_url if btn_url else self.entry_url.get() if url == 'home': - adjust_history = 1 if btn_url else None + adjust_history = None if btn_url else 1 self.load_home_screen(adjust_history) return True @@ -186,7 +186,7 @@ class GUI: data = f.read() self.entry_url.delete(0, tk.END) self.entry_url.insert(tk.END, 'home') - if event is None: + if event is not None: self.add_to_history('home') self.send_to_screen(data, '1') diff --git a/home.gopher b/home.gopher index 6e8e4d2..9abdffe 100644 --- a/home.gopher +++ b/home.gopher @@ -1,10 +1,10 @@ i false null.host 1 -i██████╗ ██╗ ██████╗ ██████╗ ███████╗██████╗ false null.host 1 -i██╔══██╗██║██╔════╝ ██╔════╝ ██╔════╝██╔══██╗ false null.host 1 -i██║ ██║██║██║ ███╗██║ ███╗█████╗ ██████╔╝ false null.host 1 -i██║ ██║██║██║ ██║██║ ██║██╔══╝ ██╔══██╗ false null.host 1 -i██████╔╝██║╚██████╔╝╚██████╔╝███████╗██║ ██║ false null.host 1 -i╚═════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ false null.host 1 +i██████╗ ██╗ ██╗██████╗ ██████╗ ██████╗ ██╗ ██╗ false null.host 1 +i██╔══██╗██║ ██║██╔══██╗██╔══██╗██╔═══██╗██║ ██║ false null.host 1 +i██████╔╝██║ ██║██████╔╝██████╔╝██║ ██║██║ █╗ ██║ false null.host 1 +i██╔══██╗██║ ██║██╔══██╗██╔══██╗██║ ██║██║███╗██║ false null.host 1 +i██████╔╝╚██████╔╝██║ ██║██║ ██║╚██████╔╝╚███╔███╔╝ false null.host 1 +i╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══╝╚══╝ false null.host 1 i false null.host 1 i##########CONTENT PORTALS########## false null.host 1 i false null.host 1 From 395fd719aec468493899a7e0a6ac18a68d3b85d1 Mon Sep 17 00:00:00 2001 From: sloumdrone Date: Thu, 25 Oct 2018 20:30:07 -0700 Subject: [PATCH 5/8] Added code for processing images, at the cost of adding dependencies --- connect.py | 6 +++++- go.config.json | 2 +- gui.py | 42 ++++++++++++++++++++++++++++++++---------- parser.py | 2 +- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/connect.py b/connect.py index bafe28a..d9e0adf 100644 --- a/connect.py +++ b/connect.py @@ -12,7 +12,11 @@ class connect: socket_conn.connect((host, port)) socket_conn.sendall((resource + '\r\n').encode('utf-8')) - response = socket_conn.makefile(mode = 'r', errors = 'ignore') + if itemtype in ['I','p','g']: + response = socket_conn.makefile(mode = 'rb', errors = 'ignore') + else: + response = socket_conn.makefile(mode = 'r', errors = 'ignore') + try: self.raw_response = response.read() self.filetype = itemtype diff --git a/go.config.json b/go.config.json index 89f1b31..52ffd5d 100644 --- a/go.config.json +++ b/go.config.json @@ -1 +1 @@ -{"favorites": [], "last_viewed": "gopher://gopher.floodgap.com:70/1/"} \ No newline at end of file +{"favorites": [], "last_viewed": "gopher://sdf.org:70/1/users/cat/"} \ No newline at end of file diff --git a/gui.py b/gui.py index e3c6d80..befef71 100644 --- a/gui.py +++ b/gui.py @@ -7,6 +7,8 @@ import time import sys import json import os.path +from io import BytesIO +from PIL import Image, ImageTk class GUI: def __init__(self): @@ -136,6 +138,7 @@ class GUI: return True parsed_url = self.parser.parse_url(url) + print(parsed_url) if not parsed_url: # To do: build errors class to handle displaying errors @@ -225,16 +228,17 @@ class GUI: return False types = { - '0': '( TEXT )', - '1': '( MENU )', - '3': '( ERROR)', - '7': '( INTR )', - '9': '( BIN )', - 'g': '( GIF )', - 'I': '( IMG )', - 'h': '( HTML )', - 'i': '( INFO )', - 's': '( SOUND)' + '0': '( TXT )', + '1': '( MNU )', + '3': '( ERR )', + '7': '( INT )', + '9': '( BIN )', + 'g': '( GIF )', + 'I': '( IMG )', + 'h': '( HTM )', + 'i': '( INF )', + 's': '( SND )', + 'p': '( PNG )' } self.site_display.config(state=tk.NORMAL) @@ -282,12 +286,22 @@ class GUI: self.site_display.config(state=tk.DISABLED) + def show_image(self, data): + self.current_image = self.build_image(data) + self.site_display.config(state=tk.NORMAL) + self.site_display.delete(1.0, tk.END) + self.site_display.image_create(tk.END, image = self.current_image) + self.site_display.config(state=tk.DISABLED) + + def send_to_screen(self, data, itemtype='1', clear=True): if itemtype == '0': self.show_text(data) elif itemtype in ['1','3']: data = self.parser.parse_menu(data) self.show_menu(data, clear) + elif itemtype in ['p','I','g']: + self.show_image(data) def update_status(self, event, href=False): @@ -318,6 +332,14 @@ class GUI: self.site_display.config(cursor="arrow") e.update_idletasks() + def build_image(self, bytes_str): + stream = BytesIO(bytes_str) + print(type(stream)) + pilimage = Image.open(stream) + tkimage = ImageTk.PhotoImage(pilimage) + return tkimage + + #--------Start file handling methods------------ diff --git a/parser.py b/parser.py index 5251883..fc08caf 100644 --- a/parser.py +++ b/parser.py @@ -16,7 +16,7 @@ class parser: def parse_url(self, url): # Take in a URL and output a dict of the url parts - regex = r'^(?P(?:gopher:\/\/)?)?(?P[\w\.\d]+)(?P(?::\d+)?)?(?P(?:\/\d)?)?(?P(?:\/[\w\/\d\-?.]*)?)?$' + regex = r'^(?P(?:gopher:\/\/)?)?(?P[\w\.\d]+)(?P(?::\d+)?)?(?P(?:\/[\dgIp])?)?(?P(?:\/[\w\/\d\-?.]*)?)?$' match = re.match(regex, url) protocol = match.group('protocol') From 7f40c91e4b9717954dc64fa9d311212010cc4d9d Mon Sep 17 00:00:00 2001 From: Brian <21182475+sloumdrone@users.noreply.github.com> Date: Thu, 25 Oct 2018 20:56:13 -0700 Subject: [PATCH 6/8] Updated dependencies and project status --- README.md | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 59f9998..e5a841e 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ -# Digger +# Burrow A client/browser that accesses _gopherspace_. It is under current early stage development. - ![Digger browser](http://brianmevans.com/files/digger.png "Digger v0.1.5 main window") + ![Burrow browser](http://brianmevans.com/files/digger.png "Burrow v0.1.5 main window") ## Gopher [_Gopher_](https://en.wikipedia.org/wiki/Gopher_(protocol)) is a communications protocol that, in the early 90s, competed (briefly) with what became the world wide web. _Gopher_ serves up files and text based menus. As such, it is much lighter weight than HTML documents and the like served over http. Due to its text based nature it also has the benefit of being reliable in its visual output and style, and for being relatively accessible. -## Digger -The following is a list of current and future Digger features: +## Browser Features +The following is a list of current and future Burrow features: - Tk based GUI - Back button, move backwards in session history - Forward button, move forward in session history - Refresh button, will be replaced by a favorite button __(non-functional)__ - - Home, shows favorites and is a start page __(non-functinoal)__ + - Home, shows favorites and is a start page - An address bar, on _ENTER_ submits a request for a _gopher_ page - Settings button __(non-functional)__ - A display area for the requested information - - Scroll bar, scrolling works, but no bar is present __(non-functional)__ + - Scroll bar - A status bar to display various information - Links to menus and files - On hover, link destination shows in status bar @@ -30,7 +30,7 @@ The following is a list of current and future Digger features: - Menus Pages and files - Menus display correctly and quickly - Text files display correctly and quickly - - Images files __(non-functional)__ + - Images files - Sound files __(non-functional)__ - Binary files __(non-functional)__ - HTML files, will open in default browser __(non-functional)__ @@ -56,19 +56,22 @@ The following is a list of current and future Digger features: ## Installation -Digger requires python3 to be installed on the system prior to running. - # can be prefixed with python3 - # but not required if you have python3 installed - /path/to/digger.py +Burrow requires python3 to be installed on the system prior to running. +Until some bundling/setup file creation occurs, you will also need to add: + + pip3 install pillow + + On some linux distributions an additional package for tkinter may be required. If you get a console error complaining about tkinter try the following (or equivalent for your package manager): - sudo apt-get python3-tk -Once up an running you should be good to go. + sudo apt-get install python3-tk + sudo apt-get install python3-pil.imagetk + ## Distribution -Digger's primary system target is linux. Once a version 1.0 is reached the plan is to distribute primarily through [Snapcraft](https://snapcraft.io/) packages. +Burrow's primary system target is linux. Once a version 1.0 is reached the plan is to distribute primarily through [Snapcraft](https://snapcraft.io/) packages. Some version of windows executable may come along as well, depending on configurability of build tools (py2exe, freeze, etc) for windows executables and time. From 630e93b79d235c45ef4ff091d68c7e963ca724f6 Mon Sep 17 00:00:00 2001 From: sloumdrone Date: Sat, 27 Oct 2018 15:33:01 -0700 Subject: [PATCH 7/8] Major updates to styling. Uses a dark code editor theme. Light theme coming soon. --- btn_back.png | Bin 2816 -> 0 bytes btn_forward.png | Bin 2791 -> 0 bytes btn_home.png | Bin 1734 -> 0 bytes btn_menu.png | Bin 4474 -> 0 bytes btn_refresh.png | Bin 1856 -> 0 bytes go.config.json | 2 +- gui.py | 76 +++++++++++++++++++++++++------------------ images/back.png | Bin 0 -> 979 bytes images/favorite.png | Bin 0 -> 1562 bytes images/favorite2.png | Bin 0 -> 1082 bytes images/forward.png | Bin 0 -> 3420 bytes images/forward2.png | Bin 0 -> 1066 bytes images/home.png | Bin 0 -> 1080 bytes images/home2.png | Bin 0 -> 1088 bytes images/settings.png | Bin 0 -> 963 bytes 15 files changed, 45 insertions(+), 33 deletions(-) delete mode 100644 btn_back.png delete mode 100644 btn_forward.png delete mode 100644 btn_home.png delete mode 100644 btn_menu.png delete mode 100644 btn_refresh.png create mode 100644 images/back.png create mode 100644 images/favorite.png create mode 100644 images/favorite2.png create mode 100644 images/forward.png create mode 100644 images/forward2.png create mode 100644 images/home.png create mode 100644 images/home2.png create mode 100644 images/settings.png diff --git a/btn_back.png b/btn_back.png deleted file mode 100644 index 3b193303bfec40befb64f6621bdd72e3a22ca288..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2816 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 diff --git a/btn_home.png b/btn_home.png deleted file mode 100644 index 2536137f6035941816fe34e35599bbbfdfd3c955..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1734 zcmV;%208hOP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=K1k{clmhWDHzM?exna2%RPRc?^u=ZD7avFA~h zNfucc%0R1y1o{NFUH|oSxF0B##k-)EVvf{eU4?J&=uz&j7Yvc0 zm-8|9tzRJ5`vePHemu&Z)yvaqh%Upc$n-qNTQ6-x!l#_}v&(y432(NuKU}xj+qRyb zgCQ7AVNcj`7ZQ&)PA`HLNRl#TtA%5<5#!kx3Gzwo+%k56Cl?Iz+X%387DyRv> ze659+-@N=)O7PqWnoKZ8nDMTJs^M#R}`DBp~1;iBcqQ%tnMb zCh#d(IZNOJ1W1(|xdApJ5pWVu-NXVh0qQR+Z zRMVtcOOzO*#uzQmJF1viFtuoA$+DFsDI`rXSxPCVk~s^mFwHTW%PHqv3Y-?0E|^`Q zQOd!G9O2L-9(JT7AEhFFYN%RcwVG zNp3RpnQaIrz5r^2bs@1$?{?r#me4807!4807!4807!4E;Zb27Wx?ucrGAU)qr* zX%2~m00006VoOIv0RI600RN!9r;`8x010qNS#tmYE+YT{E+YYWr9XB6000McNliru z;tCBH0V!=l%#8p518Ye{K~z}7-PliPT~!dr@lRfo8dF>-(xxaVjaXViyHH$c4K4%= zLKOr-NKsshLbIsdxUYKyZo9VBO)Fhl6hz!;wOX)ZHLcn_9iRU^kw?qk-#N z1b8`+=kOg)V-z!WVCw+(1mMNA9k?I8=xj4)4pSJzPdJ%2f`4vT7&PLYH*hM`)||3B zZ8l?1MOR;RuS&WymBY9ck1VXR%|+L6ba$;3t{Ve5j>+2qwzy43_dsmb_S`_G09EC&sSkx$3y-*xJ2ROfHpueK>$ulDSo60e|2le2qs^AFEV} zPCS_3Z}4|nDS0zNT*q@4aWM68Yo0IG^yH=3?rG=24s6H!*j4f0FW}E)f5A z72!TCz`av|cna5WtfnV7#r9{oUIgsGa~RCRUlr~sUJl$84&!q?iDzTvlLUSh$VQmH z#m_~+ZoG;Ya90hueFeA=^6tmP(9?muRui%dzu>DR8S&bYk~MZ7zvEDnX$ZgK4EAGl z!N}&kJCi;`8FMH)=1bP>Ba3IUKf8>wQu1E<9>9705Rl$BS-C%b&Sy-2bd;5#lj+-% zXR=>wH((<1)gNy(;Rf5l6(I);kVDaNI6B+eI~}H04DP|}%d8yDLb@Gq6=LpKp4-Vx z;;9&uoA59mTW0<3!IudU0ykb#>2D+}&n^*jWzvkvv(a&*#MXG2TJ>%ut!x?W#EC+E zt16Qjb0Rv**7z1r6Yz3cnats@W|glg(-}Kgp>K<)2{@huPL;u{JqcEE!j&+!04Gwp zr`Ewycq%LtMZnpl_SiZA91F{A5wKZ%&Q+Y|wUW6oG;8uLyOz;Z@auOF@Ol_Vm)Z}` z;M43*_O#h@HFB%4EB))961Oa~m7B-+VVGG8Z~+&xQ18guWT{&C8lGRlT}wv*+tt9) z*uGGvds}wNlO>ynm9*u~$iuO{X2;Hzwq=i<8}i`3A+A0)#O2YtxPDvr7nuL6E8shF ciN5aTUl$rNVkThyj{pDw07*qoM6N<$g46ypqyPW_ diff --git a/btn_menu.png b/btn_menu.png deleted file mode 100644 index 7d9694f81963dac154b9f86bcd57a139a9ffbc9f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4474 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 u6?Mw#U^dGmzyguMcF@$', self.update_status) x.bind('', self.clear_status) + x.config(activebackground=self.BG) self.entry_url.bind('', self.execute_address) self.btn_back.bind('', self.go_back) self.btn_forward.bind('', self.go_forward) @@ -100,7 +110,7 @@ class GUI: self.btn_back.pack(side=tk.LEFT, padx=(0,20)) self.btn_forward.pack(side=tk.LEFT, padx=(0,20)) self.btn_home.pack(side=tk.LEFT, padx=(0,20)) - self.entry_url.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=10, ipadx=10) + self.entry_url.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=5, ipadx=10) self.btn_favorite.pack(side=tk.LEFT, padx=(10,10)) self.btn_menu.pack(side=tk.LEFT) self.scroll_bar.pack(side=tk.RIGHT,fill=tk.Y) @@ -117,12 +127,11 @@ class GUI: def add_assets(self): - self.img_back = tk.PhotoImage(file='./btn_back.png') - self.img_forward = tk.PhotoImage(file='./btn_forward.png') - self.img_favorite = tk.PhotoImage(file='./btn_refresh.png') - self.img_menu = tk.PhotoImage(file='./btn_menu.png') - self.img_home = tk.PhotoImage(file='./btn_home.png') - self.img_menu = tk.PhotoImage(file='./btn_menu.png') + self.img_back = tk.PhotoImage(file='./images/back.png') + self.img_forward = tk.PhotoImage(file='./images/forward.png') + self.img_favorite = tk.PhotoImage(file='./images/favorite.png') + self.img_home = tk.PhotoImage(file='./images/home.png') + self.img_menu = tk.PhotoImage(file='./images/settings.png') self.message_bar_content = tk.StringVar() self.message_bar_content.set('Ready.') @@ -138,13 +147,15 @@ class GUI: return True parsed_url = self.parser.parse_url(url) - print(parsed_url) if not parsed_url: # To do: build errors class to handle displaying errors # return errors.url_error return False + if parsed_url['type'] == '7': + self.send_to_screen(parsed_url, parsed_url['type']) + response = self.conn.request(self.parser.resource, self.parser.host, self.parser.filetype, self.parser.port) if not response: @@ -282,7 +293,7 @@ class GUI: def show_text(self, data): self.site_display.config(state=tk.NORMAL) self.site_display.delete(1.0, tk.END) - self.site_display.insert(tk.END, data) + self.site_display.insert(tk.END, data[:-2]) self.site_display.config(state=tk.DISABLED) @@ -302,6 +313,8 @@ class GUI: self.show_menu(data, clear) elif itemtype in ['p','I','g']: self.show_image(data) + elif itemtype == '7': + pass def update_status(self, event, href=False): @@ -328,13 +341,12 @@ class GUI: def hoverlink(self, event, href, tag_name): self.update_status(event, href) e = event.widget - e.tag_config(tag_name, underline=1) + e.tag_config(tag_name, underline=1, foreground=self.LINK) self.site_display.config(cursor="arrow") e.update_idletasks() def build_image(self, bytes_str): stream = BytesIO(bytes_str) - print(type(stream)) pilimage = Image.open(stream) tkimage = ImageTk.PhotoImage(pilimage) return tkimage diff --git a/images/back.png b/images/back.png new file mode 100644 index 0000000000000000000000000000000000000000..59c2546eb93696c7fe40702c8c40792d7e253fdd GIT binary patch literal 979 zcmV;^11$WBP)rA586*`L7~jcov(z)DmiDQhSORvOP0h!?t28QpT#NJkbJscPdm>dbp^Ld{LqyfGccDWk=61NWj`63(Tfp z4m@0^lMaHVtRo81FmvcATrV!ROPleBmT)FO6O_90;G^;YC&neqCa3&&{O>F$r&csPJxo-@%C*-X|^_%8rL6egs|iGLn~K@f`O zj+%t?@)NLbRiq>YlQ2m-3B$;9sWO~8nPpgFMJmhic6~d}*S5r5h8uS`gzLI2y%33w zr(9wo3MU^w^`h}vSGcaS5nYO76tNVihc=_jY&OV_47VA@0dVzt&C=5Nw`1l-dC6$z zg@yxFPXgr{gK#@m3a-~IdGUt_5@33|3-{{Z!{^sy8G4Gd57_$SBbndtgS)#GKl*!Q zhrFfP*_{30BUun4u0yG=`|-`$25(=~JP?P{g`EA%(2Ef}mg~BXUbh{6y`94uFQW0t z$MPYbi0fkVy{D5P?L%MF?O0=+`LF;%h`6t2o<0Y@IogC@M7G!PVQ(nr(f|gKxGtxJ zsOxoi9IZXI;KgtsMg=f{NMFyH4>xNLrAb}Se*)$5m?necPwoH!002ovPDHLkV1j?Z B#z6o8 literal 0 HcmV?d00001 diff --git a/images/favorite.png b/images/favorite.png new file mode 100644 index 0000000000000000000000000000000000000000..13c1a1f2ff0e7bdf1365a08cc143769d6787df63 GIT binary patch literal 1562 zcmV+#2IcvQP)qxiol9mh~Rb+O^H%Zz*MmUqW(do#fn&IDrky|2Od$=h!+;MiU~@j zwYH@-QcF{hC|4+^wqlW{U~FA0tYrm71Yu#>*<0%$(SIUpGunJEY&5D5Sc0S*O*Fx6LWzEzO5 z_k{rj%t@Ae&q`@xlwLkWDP{l`s})NjAPf*YTlHPPUeq*xmuDUnfI2!Eiv$Qsq+XoT zYdFL4{s2k*c@OXD63yi040Su>u$%#X77GwXK)qqPeeHx`wcHRbeeDd(whO)8?FfqP zpr^AP2-Z%)W;5ZC+3HUn7Ae5^*hN}6B4kmAzzqkS)E|H3&nUgsoX0R9D3QysYavUr zon?W$;{QUJ`pncG5(-)!cxIq|PsX!AA>nAX0E@{e7&|*v#umMZQb z$z;ZPA-aAfBq|94JZp!MaxCyN8gAUFJ{w=2pWXOG4umJIiu9clSFATn)fOBfhH-N{ z79!z!pp?tV2CaQa3I27t)g}P5*#amTmdxQb1!q^Tz+sTx|KmsfwaSEwqq`nHGYKcX zyLz483M6T}Oy0#b>c0UL*v!ubysh zs99Q3u&4LARS=$-?x`L(<;xDg(0Lsu8&ygyCms-8);m1FI+6{)leF%|c|h_)i7h5; zK5|wx)s-E(fBVMzisLy?+?dbo1K|tPkWX;vKA??SW9YI#FKj)IgTq=#A%G(x>9a$W z8TSREoP=zIoV7iuF3##``TJmH;m6{D8c{_0M`>N5bMrktR1mlk4;GsQi zM6|K$i=~x^x18*EBXS=>2^m_&%#@oRN`=c>XYw&l<-i_u+;FHsj6mns%A&Vwj%+L& z6u_8e+3(3G#(pNprH6P#%MQ3-LXgxp3nT~v8?FMHphPgvHzet%Cy4t~zo| zs-3!=We||drLG3!yo98<%t*P}1lo(KNLncPT-UAI%at4EM-2*K+?Jv`Ze&Op?P9q5 z@w~)QX1K1Aqx^vV9-y9aSWgqvX6bD#$@ly1WKQ>h00QDtJ%`1wc!1j;=S(yLp_KBp zu>^VX0)m3I(Fph+tLU&Ex?l)IYgO^=E4dlv0|L-2*%qywmQV&6r@OHSu;n8?dEjj{ zG}N6b%zSuP*ES<{<8Cx2JVyW63T*(mIwJvjbiHzC)wV_31_ThY^59lw*sN@#RKk(w zf`G;Y^xUf{-Q9NguiVn(xqakZB~C zn7H}GS(SEDJd3Rl_Lc(F{QIS*x}QHhpTGOo)6UO%f1PZ2aLBrV>2VwNW{I+kTni)* zzCY?)Te!p6FM!~f#jO9lbc0&0_6tP#^8TfCeP>%sQEA};5jHjhQ0Rb%UE;wO7ZU6uP M07*qoM6N<$f@k~X#Q*>R literal 0 HcmV?d00001 diff --git a/images/favorite2.png b/images/favorite2.png new file mode 100644 index 0000000000000000000000000000000000000000..3fdfcdb1b5e11548e21735b0223a7ec8557b27ef GIT binary patch literal 1082 zcmV-A1jYM_P)dFrv$m$L*W44DG?%^STaqTR`M&cNec|DJFtdLxAM zgySUS-iibev5tmgggt~+yphVYIN=MzArcbg7C_|dXt+qIUmXwb3L;#5oesrASq{C*UtKP-->g{Vpifs{h@IE@J5D zF=%3O)RdWdMX+M^0u!*49=?&|z2UVM=wIHL^V`^uL-^y;6&OoOvBqrS6|sv6I7<)j zNqVg*h00V0mDvR2>#stoR_Dyeqfy+ua0#KAS*QX**osP^lorv)1hAK{VYylD#s+BC zuM>!s#oQ+(SRxWZ%;QD)w+9G0oQ0M-nE)@AzG^vU-SIYxS69m-&Nufz4>8&I9n4zY za??I0AX(_otMocpI@+N&udFb#RpWsxYia*+p~{40=0gHL~ft#lMD8mTPtiN1un zA}IIwc1xyLk}Bii;UkC!f(6%@HCj}c3CHT>PD7Q&gv#zNt_a-x@MDCgXGGT+OEsu6 z82h*jQEB~Jn76fY32>b0$JDS>a$Si^{XTao);Bl7(A>l&;IDxn@b}g0Qp;E=u1r-7h8J+qT}GPVUcfc~=LDsw#!t z9{v6rrW_+V^R^9*sB7B-ZACeJ6OVD{lU_0MI1$@hdwYK8Zb($N3d|~L-LDQL6NzQl zD3nUr>L zOEt0mxm#GN>$x`^9(Q@&Bh;n~*K-Q@JkP%W0gNhu^5|;&nE(I)07*qoM6N<$f)^VC A3IG5A literal 0 HcmV?d00001 diff --git a/images/forward.png b/images/forward.png new file mode 100644 index 0000000000000000000000000000000000000000..be4b28407199e3d85cd98f9180604b0e7b212027 GIT binary patch literal 3420 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 literal 0 HcmV?d00001 diff --git a/images/forward2.png b/images/forward2.png new file mode 100644 index 0000000000000000000000000000000000000000..cf9e019150ff33fc8bf9678ebf4923dce8639d09 GIT binary patch literal 1066 zcmV+_1l9YAP) z-A|KO9LK*cSSb^0izG_rRgy(9zEAMw$~%^92@Wp|in2Sl8O?6y{(@=Z&D2FMrcpGD zQ6lQH?PA$t)cFz#3L^LtVb#K>*f&~gDSg@Z)RsLhefpeY{UzU=CwZRd{66RWe&=`2 z(LvJehrYM4`Gnd^t)?m{Q~b4uvQhV`KdGOWe6!Cf)2~j#aV?tz)OKpFB=013fjUT# zhqD5p<9TepquQuE*_leQFm;^zmLNW906JgBri=P;mX1^HG1W+jK}`TUuVeEYWzLcF zgt@2&LflaWh>!fQ_uyO!u~|72(*W_9cO!Sxu4W<}>k~Pl1USm(E9q`@Iux#~Md0ZW zB7T3i5XT6xcN!qR)OwLcwi~RAQPR8}VXqI4OTS?1pO@JHh}E%5EYSqOSvJkt^k68n zqGZQsvFB(g1o!V(;eRlYO&4bguuB)OaHEhfGD!d<-90F-S&D)MCiw34!Sly8AQF-3 zLKLe~0kE47r&8Y*EUiNRLW}C(`E>ZfsudW#cp0NTy;$@4E?BCoFlir!^U|-fd9gYK&;+n_hh(d?oBTqj=Kx&vKa9+q$W6y;ksz0Hd*=Ec97dC?&NMDndneN+1N z7x36eIKnHce@CNHjPskQV#=3n+<@Aq##nYsz|50WtlWaj{ zIYZKd0u%Fs;c+o9^n^`g9s!W{&AR{yn>)VrpPM6^_F2f2g!+_g^o|7H$*q*EYS7xY83esvP56AhzBOYhcKIjp!j=APX{o73 z-QKpOQf#|%GZwOFIIQU$Z%eT?u^F9VvoqaS-ar60=W9e#)^P$rSa>R=8t)AD)5k{+ zH%o2DZ-PM#JH`-FB%8Ua67~DPl!|v63)AuHh9igjr4J-QZn;B*5UntsUJq;Cdc5i% zV7z_WE|cvCjy~oqt0P&6vHhFj@N-7q!Jg3RI*va1NEV0CJd{QlhUYE^#+e(lFmWi| z!qHFfP_sIgivl#_cEQP2lBM$oI#)lIr_4mgi-~}Em z6R^8g@1dd~wA0CfM9ifD6d;h($ literal 0 HcmV?d00001 diff --git a/images/home.png b/images/home.png new file mode 100644 index 0000000000000000000000000000000000000000..65e751d14e0a6426054df47199a933f96e431801 GIT binary patch literal 1080 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 lm-ufB_FC*^c(KFZmfvikhX}koH Date: Mon, 29 Oct 2018 19:19:03 -0700 Subject: [PATCH 8/8] Added support for h itemtype to open in web browser --- go.config.json | 1 - gui.py | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) delete mode 100644 go.config.json diff --git a/go.config.json b/go.config.json deleted file mode 100644 index 89f1b31..0000000 --- a/go.config.json +++ /dev/null @@ -1 +0,0 @@ -{"favorites": [], "last_viewed": "gopher://gopher.floodgap.com:70/1/"} \ No newline at end of file diff --git a/gui.py b/gui.py index 6b22c2c..2ae690a 100644 --- a/gui.py +++ b/gui.py @@ -9,6 +9,7 @@ import json import os.path from io import BytesIO from PIL import Image, ImageTk +import webbrowser as wb class GUI: def __init__(self): @@ -40,8 +41,8 @@ class GUI: #create and configure root window - self.root = tk.Tk(className='Digger') - self.root.title('Digger') + self.root = tk.Tk(className='Burrow') + self.root.title('Burrow') self.root.geometry("1200x800") self.add_assets() @@ -153,6 +154,7 @@ class GUI: # return errors.url_error return False + if parsed_url['type'] == '7': self.send_to_screen(parsed_url, parsed_url['type']) @@ -184,6 +186,9 @@ class GUI: def gotolink(self, event, href, tag_name): + if href[:4] == 'http': + wb.open(href, 2, True) + return True element = event.widget element.tag_config(tag_name, background=self.ACTIVELINK) element.update_idletasks() # make sure change is visible @@ -271,7 +276,12 @@ class GUI: if x['port'] and x['port'][0] != ':': x['port'] = ':{}'.format(x['port']) - link = 'gopher://{}{}/{}{}'.format(x['host'], x['port'], x['type'], x['resource']) + if x['type'] == 'h': + link = '{}/{}'.format(x['host'], x['resource']) + link = 'http://{}'.format(link.replace('//','/')) + else: + link = 'gopher://{}{}/{}{}'.format(x['host'], x['port'], x['type'], x['resource']) + tag_name = 'link{}'.format(link_count) callback = (lambda event, href=link, tag_name=tag_name: self.gotolink(event, href, tag_name)) hover = (lambda event, href=link, tag_name=tag_name: self.hoverlink(event, href, tag_name))