manyterm 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
manyterm/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ from .manyterm import Terminal
2
+ #from .manyterm_tk import Terminal
manyterm/manyterm.py ADDED
@@ -0,0 +1,130 @@
1
+ import subprocess
2
+ import socket
3
+ import atexit
4
+ import uuid
5
+ import sys
6
+ import os
7
+
8
+ _MANYTERM_PORT = 16161
9
+ _MANYTERM_HOST = '127.0.0.1'
10
+
11
+
12
+ if sys.platform not in ["linux", "win32", "darwin"]:
13
+ raise Exception(f"Platform \"{sys.platform}\" not supported for package manyterm")
14
+
15
+ # server for sending text and listening for new connections
16
+ # all static methods so there is only one instance
17
+ class _Server:
18
+
19
+ _running = False
20
+ _socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
21
+ _connections = {}
22
+
23
+ @staticmethod
24
+ def start():
25
+ if not _Server._running:
26
+ _Server._running = True
27
+ _Server._socket.bind((_MANYTERM_HOST, _MANYTERM_PORT))
28
+ _Server._socket.settimeout(10)
29
+ # close server and windows on exit
30
+ atexit.register(_Server.stop)
31
+
32
+
33
+ # listen for new connections
34
+ @staticmethod
35
+ def listen():
36
+ data, addr = _Server._socket.recvfrom(1024)
37
+ if data is not None:
38
+ _Server._connections[data.decode('utf-8')] = addr
39
+ else:
40
+ assert Exception("Could not bind window to server")
41
+
42
+
43
+ @staticmethod # print to a window based on the uid
44
+ def send(uid, b):
45
+ addr = _Server._connections.get(uid, None)
46
+ if addr is not None:
47
+ _Server._socket.sendto(b, addr)
48
+ else:
49
+ assert Exception("Cannot print to closed terminal")
50
+
51
+ @staticmethod # close a window based on the uid
52
+ def close(uid):
53
+ _Server._socket.sendto(bytes(0), _Server._connections.get(uid))
54
+ _Server._connections.pop(uid)
55
+
56
+ @staticmethod
57
+ def stop(): # close all windows
58
+ for key in _Server._connections:
59
+ _Server._socket.sendto(bytes(0), _Server._connections.get(key))
60
+ _Server._socket.close()
61
+ _Server._running = False
62
+
63
+
64
+ class Terminal:
65
+ _path = os.path.abspath(__file__)
66
+
67
+ def __init__(self, title="Terminal"):
68
+ """
69
+ Start a new terminal window
70
+ :param title: the title of the window (linux only)
71
+ """
72
+ # start server (if not running)
73
+ _Server.start()
74
+ # make uid for self
75
+ self._uid = str(uuid.uuid4()) # could also use uuid1()
76
+ # open window
77
+ if sys.platform == "linux":
78
+ subprocess.run(('gnome-terminal', '-t', f'{title}', '--', 'bash', '-c', f'{sys.executable} {self._path} {self._uid}'), shell=False)
79
+ elif sys.platform == "win32":
80
+ subprocess.run(('start', '/wait','cmd', '/c', f'{sys.executable} {self._path} {self._uid}'), shell=True)
81
+ elif sys.platform == "darwin":
82
+ subprocess.run(("osascript", "-e", f"tell application \"Terminal\" to do script \"{sys.executable} {self._path} {self._uid};exit\""), shell=False)
83
+ _Server.listen() # wait for window to open and listen for new connection
84
+
85
+ def print(self, txt, end="\n"):
86
+ """
87
+ Prints to the window
88
+ :param txt: the string to print
89
+ :param end: the end of the string
90
+ """
91
+ _Server.send(self._uid, bytes(txt+end, 'utf-8'))
92
+
93
+ def close(self):
94
+ """
95
+ Closes the window
96
+ """
97
+ _Server.close(self._uid)
98
+
99
+ def close_all(self):
100
+ """
101
+ Closes all windows
102
+ """
103
+ _Server.stop()
104
+
105
+
106
+ # called from subprocess to open window
107
+ # acts as a client to connect to the server and listens for data to print
108
+ class _TerminalClient:
109
+ def __init__(self):
110
+ if len(sys.argv) != 2:
111
+ raise Exception("Usage: python3 manyterm.py <uid>")
112
+
113
+ self._uid = sys.argv[1]
114
+
115
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
116
+
117
+ # send uid to server
118
+ s.sendto(bytes(self._uid, 'utf-8'), (_MANYTERM_HOST, _MANYTERM_PORT))
119
+
120
+ while True:
121
+ data, addr = s.recvfrom(1024)
122
+ if data == bytes(0):
123
+ break
124
+ else:
125
+ print(data.decode('utf-8'), end="")
126
+ s.close()
127
+
128
+
129
+ if __name__ == '__main__':
130
+ _TerminalClient()
@@ -0,0 +1,71 @@
1
+ from time import sleep
2
+ import tkinter as tk
3
+ from tkinter import ttk
4
+ import threading
5
+
6
+
7
+ class Terminal(threading.Thread):
8
+
9
+ def __init__(self, title="Terminal", height=20, width=100, font=("Courier New", "12"), backgroundColor="gray5", textColor="snow", allowClosing=True, redirectClosedPrints=True):
10
+ #params
11
+ self.title = title
12
+ self.height = height
13
+ self.width = width
14
+ self.font = font
15
+ self.backgroundColor = backgroundColor
16
+ self.textColor = textColor
17
+ self.allowClosing = allowClosing
18
+ self.redirectClosedPrints = redirectClosedPrints
19
+ #internal variables
20
+ self._root = None # main window
21
+ self._tb = None # text box
22
+ self.isOpen = False # if the window is open
23
+ self._safeExit = False # if the window is safe to exit
24
+ threading.Thread.__init__(self, daemon=True) # kill child thread on main thread exit
25
+ self.start()
26
+ sleep(0.5) # wait for window to open
27
+
28
+ def close(self):
29
+ if self.isOpen and self._safeExit: # safeExit is True is no operations are being done (i.e. print)
30
+ self.isOpen = False
31
+ self._root.quit()
32
+ self._root.update()
33
+ self._root = None
34
+
35
+ def run(self):
36
+ self._root = tk.Tk()
37
+ if self.allowClosing: self._root.protocol("WM_DELETE_WINDOW", self.close)
38
+ else: self._root.protocol("WM_DELETE_WINDOW", lambda: None)
39
+ self._root.title(self.title)
40
+ self._tb = tk.Text(self._root, height=self.height, width=self.width, wrap="none", font=self.font)
41
+ self._tb.pack(side="left", fill="both", expand=True)
42
+ self._tb.configure(state="disabled", bg=self.backgroundColor, fg=self.textColor)
43
+ sizegrip = ttk.Sizegrip(self._tb)
44
+ sizegrip.configure(cursor="sizing")
45
+ sizegrip.bind("<1>", self._resize_start)
46
+ sizegrip.bind("<B1-Motion>", self._resize_update)
47
+ self.isOpen = True
48
+ self._safeExit = True
49
+ self._root.mainloop()
50
+
51
+ def print(self, toPrint, end="\n"):
52
+ if self._root is None or not self.isOpen:
53
+ if self.redirectClosedPrints: print(toPrint, end=end) # redirect output
54
+ #return print(f"Terminal \"{self.title}\" is closed")
55
+ #raise Exception(f"Terminal \"{self.title}\" is closed")
56
+ else:
57
+ self._safeExit = False # needed so that we don't kill mainloop while printing
58
+ self._tb.configure(state="normal")
59
+ self._tb.see("end")
60
+ self._tb.insert("end", f"{toPrint}{end}")
61
+ self._tb.configure(state="disabled")
62
+ self._safeExit = True
63
+
64
+ def _resize_start(self, event):
65
+ self._x = event.x
66
+ self._y = event.y
67
+
68
+ def _resize_update(self, event):
69
+ delta_x = event.x - self._x
70
+ delta_y = event.y - self._y
71
+ self._tb.place_configure(width=self._tb.winfo_width() + delta_x, height=self._tb.winfo_height() + delta_y)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Tyler Bowers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.1
2
+ Name: manyterm
3
+ Version: 1.0.0
4
+ Summary: manyterm - spawn multiple terminals to print to
5
+ Author: Tyler Bowers
6
+ Author-email: tylerebowers@gmail.com
7
+ License: MIT
8
+ Project-URL: Source, https://github.com/tylerebowers/manyterm-python
9
+ Project-URL: PyPI, https://pypi.org/project/manyterm/
10
+ Keywords: python,terminal,print
11
+ Classifier: Topic :: Terminals
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Natural Language :: English
18
+ Requires-Python: >=3.8
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+
22
+ # Manyterm
23
+ Spawn multiple terminals to print to.
24
+ Compatible with Windows, Linux, and MacOS.
25
+ ### Installation
26
+ `pip install manyterm`
27
+ ### Usage
28
+ ```py
29
+ import manyterm
30
+
31
+ t1 = manyterm.Terminal() # init a new window
32
+
33
+ t1.print("Hello World") # print to the window
34
+
35
+ t1.print("supercalifragilisticexpialidocious", end="") # default end="\n"
36
+
37
+ t1.close() # close the window
38
+ ```
39
+ ### Description
40
+ This program creates multiple terminals in python. New terminals can only be printed to (no input). `tests/test.py` contains an example of how to use this program. A server is used to print to client windows based on a uuid. When a new window object is made `manyterm.Terminal()` the program check to make sure that the server is running, then assigns itself a uuid, and then the program calls itself from a new terminal using the uuid as an argument. The new program contacts the server with its' uuid and the connection is made.
41
+ ## Windows 11
42
+ ![Picture showing windows example](tests/screenshot-win.png)
43
+ ## Linux (Ubuntu 23.10)
44
+ ![Picture showing windows example](tests/screenshot-linux.png)
45
+ ## MacOS Sonoma
46
+ ![Picture showing windows example](tests/screenshot-macos.jpg)
@@ -0,0 +1,8 @@
1
+ manyterm/__init__.py,sha256=j0xHtJEbaS7LjUBZvFVIuRnvvmgJCup7nGkamf4It1I,65
2
+ manyterm/manyterm.py,sha256=bD3JLdL5tp4kWXQp9jlmiUNp-txtNCwOuC7HJc85C1A,3968
3
+ manyterm/manyterm_tk.py,sha256=oyTN9AYgMBqPcgDerTML5GGDIipfqv94zatd53pw4fY,2962
4
+ manyterm-1.0.0.dist-info/LICENSE,sha256=hjzpaUekXMmk3H45s6cm049FnUxrJ4QDOhv_8ClITO8,1069
5
+ manyterm-1.0.0.dist-info/METADATA,sha256=zo0_srBLSml6sc0f0FVBqdkq7STOD8I-DBCRjfJ9VO4,1887
6
+ manyterm-1.0.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
7
+ manyterm-1.0.0.dist-info/top_level.txt,sha256=A9uJ6-Un6lnPFdgdge78cCzPqG4bbp8yYE9Ky1YOl4M,9
8
+ manyterm-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.43.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ manyterm