VitaminDude

bad gemini browser

Aug 21st, 2026
54
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.05 KB | None | 0 0
  1. #you could try gemini://namno.duckdns.org to see how much of a mess this is
  2. import tkinter as tk
  3. from tkinter import ttk
  4. from tkinter import font
  5. import socket
  6. import ssl
  7. from urllib import parse as url
  8. import functools
  9.  
  10. default_scheme="gemini"
  11. print(tk.TkVersion)
  12.  
  13.  
  14. class Document():
  15. def Raw():
  16. return ""
  17. def Type():
  18. return ("about","blank",None)
  19. def Tree():
  20. return None
  21.  
  22. class Gemtext(Document):
  23. def __init__(self, raw, options=None):
  24. self.raw=raw
  25. self.options=options
  26. self.textarray=[]
  27.  
  28. def parsehelp(l):
  29. if l=="":
  30. return (("",None),"")
  31. if l[0:2]=="=>":
  32. link=l[2:].lstrip().split(None, 2)
  33. if link[1]=="":
  34. link[1]=link[0]
  35. return (("=>", link[0]), link[1])
  36. if l[0]==">":
  37. level=1
  38. for c in l[1:]:
  39. if c!=">":
  40. break
  41. level+=1
  42. level=max(level,3)
  43. return ((">", level),l[level:])
  44. if l[0]=="#":
  45. level=1
  46. for c in l[1:]:
  47. if c!="#":
  48. break
  49. level+=1
  50. level=max(level,3)
  51. return (("#", level),l[level:])
  52. if l[0]=="*":
  53. level=1
  54. for c in l[1:]:
  55. if c!="*":
  56. break
  57. level+=1
  58. level=max(level,3)
  59. return (("*", level),l[level:])
  60. return (("", None),l)
  61.  
  62. self.tree=[]
  63. preformated=False
  64. for l in self.raw.split("\n"):
  65. if l[0:3]=="```":
  66. preformated= not preformated
  67. continue
  68. if preformated:
  69. self.tree.append((("```", None),l))
  70. else:
  71. self.tree.append(parsehelp(l))
  72.  
  73.  
  74. def Raw(self):
  75. return self.raw
  76. def Type(self):
  77. return("text","gemini", options)
  78. def Tree(self):
  79. return self.tree
  80. def view_document(self, canv):#refactor into document class, expose only canvas from tab
  81. print("FOO")
  82. for i in self.textarray:
  83. i[0].destroy()
  84. try:
  85. i[2].destroy()
  86. except IndexError:
  87. pass
  88. self.textarray.clear()
  89.  
  90. for el in D.Tree():
  91. if len(self.textarray)>0 and self.textarray[-1][1]==el[0]:
  92. self.textarray[-1][0].insert("end", "\n"+el[1],"tag")
  93.  
  94. else:
  95. i=tk.Text(self.txfr, borderwidth=0, height=1, relief=tk.FLAT)
  96. i.insert("end", el[1],"tag")
  97. print(el)
  98. i.tag_configure("tag", cnf=self.style_helper(el[0]))
  99. self.textarray.append((i,el[0]))
  100. for i in self.textarray:
  101. i.configure( height=i.count("0.1","end","displaylines", "update"), state="disable")
  102. print("LINECOUNT:")
  103. print(i.count("0.1","end","displaylines", "update"))
  104. #i[0]["state"]="disable"
  105. i[0].pack(expand=1, fill="both")
  106. try:
  107. i[2].pack(expand=1, fill="x")
  108. except IndexError:
  109. pass
  110. class Protocols:
  111. p={
  112. "gemini":{"port":1965,"TLS":True},
  113. "https":{"port":443,"TLS":True}
  114. }
  115. def Get(host: str, user=None, port=None, path=None, q=None, frag=None):
  116. pass
  117. def CreateTLS(host: str, port: int, callback):
  118. print(host)
  119. print(port)
  120. context=ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT)
  121. context.check_hostname=False
  122. context.verify_mode=ssl.CERT_NONE
  123. context.minimum_version=ssl.TLSVersion.TLSv1_3
  124. context.maximum_version=ssl.TLSVersion.TLSv1_3
  125. with socket.create_connection((host,port)) as sock:
  126. with context.wrap_socket(sock, server_hostname=host) as ssock:
  127. #TODO watch out for socket.herror, socket.gaierror and socket.timeout
  128. return callback(ssock)
  129.  
  130. class gemini(Protocols):
  131. scheme="gemini"
  132. defport=1965
  133. def Get(host: str, user=None, port=None, path=None, q=None, frag=None):#TODO reimplement in restartable manner
  134. header="gemini://"+host
  135. #TODO make sure using ipv6 with custom port works
  136. if port is None:
  137. port=1965
  138. else:
  139. header+=":"+port
  140. if path is None:
  141. path="/"
  142. header+="/"+path
  143. if not q is None:
  144. header+="?"+q
  145. header=header.encode("utf-8")+b"\r\n"
  146. def cb(s):
  147. mime=""
  148. body=""
  149. s.sendall(header)
  150. r= b""
  151. a=s.recv(1024)#TODO add exception handling
  152. while(a !=b""):
  153. r+=a
  154. a=s.recv(1024)
  155. #if r==b"":
  156. #TODO
  157. r=[x.removesuffix(b"\r") for x in r.split(b"\n",1)]
  158. statusdigit1=r[0][0]-b"0"[0]
  159. statusdigit2=r[0][1]-b"0"[0]
  160. if statusdigit1==1:
  161. #TODO input
  162. raise NotImplementedError
  163. elif statusdigit1==2:
  164. #success
  165. mimetemp=r[0][2:].decode()
  166. if mimetemp=="" :#TODO factor out
  167. mime=("text","gemtext",None)
  168. else:
  169. mimetemp=mimetemp.split("/",1)
  170. if len(mimetemp)<2:
  171. return Document()#TODO: raise propper error
  172. if mimetemp[1].find(";")==-1:
  173. mimetemp.append([x.strip() for x in mimetemp[1].split(";")[1:] ])
  174. mimetemp[1]=mimetemp[1].split(";")[0]
  175. else:
  176. mimetemp.append(None)
  177. if mimetemp[1].find("+")==-1:
  178. mimetemp[1]=[x.strip() for x in mimetemp[1].split("+") ]
  179. else:
  180. mimetemp[1]=mimetemp[1].strip()
  181. mime=(mimetemp[0],mimetemp[1],mimetemp[2])
  182. del mimetemp
  183. #TODO refactor decoding into it's own function
  184. if not mime[2] is None:
  185. for x in mime[2]:
  186. if x[:8].casefold()=="charset=":
  187. body=r[1].decode(x.split("=",1)[1])
  188. break
  189. if body=="":
  190. body=r[1].decode()#no charset parameter=>UTF-8
  191. else:
  192. print("CODE "+r[0].decode()+", body "+r[1].decode())
  193. raise NotImplementedError
  194. return Gemtext(body, options=mime[2])#TODO UGLYUGLYUGLY refactor this
  195. return Protocols.CreateTLS(host,port, cb)#TODO rethink this interface
  196.  
  197. protocol_table={
  198. gemini
  199. }
  200.  
  201. document_table={
  202. Gemtext
  203. }
  204.  
  205.  
  206.  
  207. class about(Protocols):
  208. def Get(host: str, user=None, port=None, path=None, q=None, frag=None):
  209. return ""
  210.  
  211. class T:
  212. #def __init__(w: W, url=None):
  213. def __init__(self, w, url=None):
  214. if url is None:
  215. url="about:blank"
  216. self.textarray=[]
  217.  
  218. self.fr=tk.Frame(w.nb)
  219. self.adress_var=tk.StringVar()
  220. self.adress=tk.Entry(self.fr, justify="left",relief="sunken",textvariable=self.adress_var)
  221. self.adress.pack(fill="x")
  222. self.adress.bind('<Return>', functools.partial(self.adress_press_enter) )
  223.  
  224. self.canv=tk.Canvas(self.fr)
  225. self.canv.pack(side="left", fill="both", expand=True)
  226.  
  227. self.scrl=tk.Scrollbar(self.fr, orient="vertical", command=self.canv.yview)
  228. self.scrl.pack(side="right", fill="y")
  229. #self.scrl.congigure(command=self.fr.yview)
  230. self.canv.configure(yscrollcommand=self.scrl.set)
  231.  
  232. self.txfr=tk.Frame(self.canv)
  233. self.canv.create_window((0, 0), window=self.txfr, anchor="nw", tags=("txfr"))
  234. def handler_fix(event):
  235. canv=event.widget
  236. canv.configure(scrollregion=canv.bbox("all"))
  237. self.canv.bind("<Configure>", handler_fix)
  238.  
  239. self.fr.pack(fill="both", expand=True)
  240. w.nb.add(self.fr, text="TODO TITLE")
  241.  
  242. def go_adress(self, adress_str):
  243. parsed=url.urlsplit(adress_str)
  244. host=parsed.netloc
  245. #TODO maybe protect against ValueError?
  246. if not parsed.port is None:
  247. host=host[0:host.rfind(":")]
  248. for p in protocol_table:
  249. if parsed.scheme==p.scheme:
  250. #TODO this will need to be reworked wthen decoding will be reworked
  251. self.view_document(p.Get(
  252. host,
  253. user=parsed.username,
  254. port=parsed.port,
  255. path=parsed.path,
  256. q=parsed.query,
  257. frag=parsed.fragment
  258. ))
  259. return
  260. print("PROTOCOL NOT FOUND")
  261.  
  262. def adress_press_enter(self, event):
  263. adress_str=self.adress_var.get()
  264. if not "//" in adress_str:
  265. adress_str=default_scheme+"://"+adress_str
  266. self.adress_var.set(adress_str)
  267. self.go_adress(adress_str)
  268.  
  269. def close_handler(self):
  270. for i in self.textarray:
  271. i[0].destroy()
  272. try:
  273. i[2].destroy()
  274. except IndexError:
  275. pass
  276. self.txfr.destroy()
  277. self.scrl.destroy()
  278. self.canv.destroy()
  279. self.fr.destroy()
  280.  
  281. @staticmethod
  282. def style_helper(S):
  283. r={"wrap":"word"}
  284. basic={
  285. "": { },
  286. "=>": {"foreground": "blue" },
  287. ">": {"lmargincolor": "cyan" },
  288. "```": {"font": "TkFixedFont" ,
  289. "wrap": "none" },
  290. "#": {"justify": "center" },
  291. "*": { }
  292. }
  293. #print(type(basic))
  294. #print(type(S))
  295. #print(S[0])
  296. for k,v in basic[S[0]].items():
  297. r[k]=v
  298. r=basic[S[0]]
  299.  
  300. if S[0]==">" or S[0]=="*":
  301. r["lmargin1"]=10*S[1]
  302. r["lmargin2"]=10*S[1]
  303. if S[0]=="#" and S[1]>1:
  304. f=font.nametofont("TkTextFont")
  305. f["weight"]="bold"
  306. if S[1]==3:
  307. f["size"]=2*f["size"]
  308. r["font"]=f
  309. return r
  310.  
  311. def view_document(self, D: Document):#refactor into document class, expose only canvas from tab
  312. print("FOO")
  313. for i in self.textarray:
  314. i[0].destroy()
  315. try:
  316. i[2].destroy()
  317. except IndexError:
  318. pass
  319. self.textarray.clear()
  320.  
  321. for el in D.Tree():
  322. if len(self.textarray)>0 and self.textarray[-1][1]==el[0]:
  323. self.textarray[-1][0].insert("end", "\n"+el[1],"tag")
  324. else:
  325. i=tk.Text(self.txfr, borderwidth=0, height=1, relief=tk.FLAT)
  326. i.insert("end", el[1],"tag")
  327. print(el)
  328. i.tag_configure("tag", cnf=self.style_helper(el[0]))
  329. self.textarray.append((i,el[0]))
  330. for i in self.textarray:
  331. logic_lines=i[0].count("1.1", "end", "lines", return_ints=True)
  332. print("LOGICAL LINES="+str(logic_lines))
  333. for j in range(1, logic_lines+1):
  334. print("LINE "+str(j)+": "+str(i[0].count(
  335. str(j)+".1",
  336. str(j)+".end",
  337. "update",
  338. "chars",
  339. "displaychars",
  340. "displayindices",
  341. "displaylines",
  342. "indices")))
  343. i[0]["state"]="disable"
  344. i[0].pack(expand=1, fill="both")
  345. try:
  346. i[2].pack(expand=1, fill="x")
  347. except IndexError:
  348. pass
  349.  
  350. class W:
  351. def __init__(self):
  352. self.w=tk.Toplevel(Manager_Instance.root)
  353. self.w.protocol("WM_DELETE_WINDOW", functools.partial(self.close_handler))
  354. self.w.geometry("640x480")
  355.  
  356. self.nb=ttk.Notebook(self.w)
  357. self.nb.pack(fill="both", expand=True)
  358. self.tabs=[T(self)]
  359.  
  360. def close_handler(self):
  361. for i in self.tabs:
  362. i.close_handler()
  363. self.nb.destroy()
  364. self.w.destroy()
  365. Manager_Instance.windows.remove(self)
  366. if len(Manager_Instance.windows)==0:
  367. Manager_Instance.root.destroy()
  368.  
  369. class Manager:
  370.  
  371. def __init__(self):
  372. self.root=tk.Tk()
  373. self.root.withdraw()#so that user can close windows in any order
  374. global Manager_Instance
  375. Manager_Instance=self
  376. self.windows=[W()]
  377. self.root.mainloop()
  378.  
  379. def main():
  380. _=Manager()
  381. #Manager_Instance.windows[0].tabs[0].go_adress("gemini://namno.duckdns.org")
  382. if __name__=="__main__":
  383. main()
  384.  
  385.  
Advertisement
Add Comment
Please, Sign In to add comment