1 Star 0 Fork 13

ryvius_key / JingTerm

forked from outersky / JingTerm 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
term.py 14.54 KB
一键复制 编辑 原始数据 按行查看 历史
from gi.repository import Gtk , Gdk, Vte, GLib, Pango
from context_menu import *
from keymap import *
import os
import asyncio
import time
import threading
# 终端内核组件
# 下面的是在VTE里面增加下划线提示的正则表达式
USERCHARS = "-[:alnum:]"
USERCHARS_CLASS = "[" + USERCHARS + "]"
PASSCHARS_CLASS = "[-[:alnum:]\\Q,?;.:/!%$^*&~\"#'\\E]"
HOSTCHARS_CLASS = "[-[:alnum:]]"
HOST = HOSTCHARS_CLASS + "+(\\." + HOSTCHARS_CLASS + "+)*"
PORT = "(?:\\:[[:digit:]]{1,5})?"
PATHCHARS_CLASS = "[-[:alnum:]\\Q_$.+!*,;:@&=?/~#%\\E]"
PATHTERM_CLASS = "[^\\Q]'.}>) \t\r\n,\"\\E]"
SCHEME = """(?:news:|telnet:|nntp:|file:\/|https?:|ftps?:|sftp:|webcal:|irc:|sftp:|ldaps?:|nfs:|smb:|rsync:|ssh:|rlogin:|telnet:|git:|git\+ssh:|bzr:|bzr\+ssh:|svn:|svn\+ssh:|hg:|mailto:|magnet:)"""
USERPASS = USERCHARS_CLASS + "+(?:" + PASSCHARS_CLASS + "+)?"
URLPATH = "(?:(/" + PATHCHARS_CLASS + "+(?:[(]" + PATHCHARS_CLASS + "*[)])*" + PATHCHARS_CLASS + "*)*" + PATHTERM_CLASS + ")?"
REGEX_STRINGS = [
SCHEME + "//(?:" + USERPASS + "\\@)?" + HOST + PORT + URLPATH,
"(?:www|ftp)" + HOSTCHARS_CLASS + "*\\." + HOST + PORT + URLPATH,
"(?:callto:|h323:|sip:)" + USERCHARS_CLASS + "[" + USERCHARS + ".]*(?:" + PORT + "/[a-z0-9]+)?\\@" + HOST,
"(?:mailto:)?" + USERCHARS_CLASS + "[" + USERCHARS + ".]*\\@" + HOSTCHARS_CLASS + "+\\." + HOST,
"(?:news:|man:|info:)[[:alnum:]\\Q^_{|}~!\"#$%&'()*+,./;:=?`\\E]+",
"git\\@" + HOST + ":" + HOST + URLPATH,
]
class Term(Vte.Terminal):
def __init__(self,title, notebook, init_dir=None, callback=None):
super(Term, self).__init__()
#绑定参数
self.title = title
self.notebook = notebook
#设置字体,貌似GTK的css不起作用,只能在代码里面设置
font_desc = Pango.FontDescription("Noto Sans Mono 12")
self.set_font(font_desc)
#设置默认启动的shell和默认目录
home = init_dir or os.getenv('HOME')
shell = Vte.get_user_shell() or os.getenv('SHELL') or '/bin/sh'
#绑定事件:
self.connect("button_press_event",self.on_button_press)
self.connect("contents-changed", self.on_contents_changed)
self.connect("selection-changed", self.on_selection_changed)
self.connect("key-press-event", self.on_key_press_event)
self.connect("window-title-changed", self.on_window_title_changed)
self.connect("scroll-event", self.on_scroll_event)
self.connect("child-exited", self.on_child_exited)
#将终端的链接等加上下划线(鼠标经过时)
self.clickable(REGEX_STRINGS)
#初始化回调函数
def _spawn_():
#VTE初始化
if not self.spawn(home, [shell]):
return
if callback:
def _f_():
callback(self)
#执行业务回调函数
GLib.timeout_add(200,_f_)
#启动
GLib.idle_add(_spawn_)
def rename(self,text, force=None):
self.notebook.rename(self,text,force)
def spawn(self, home, argv, callback=None):
if hasattr(self,'spawn_async'):
try:
self.spawn_async(
Vte.PtyFlags.DEFAULT,
home,
argv,
None,
0,
None,
callback,
-1,
)
return True
except BaseException as e:
print('-----Spawn1 Error',e)
return False
else:
try:
return self.spawn_sync(
Vte.PtyFlags.DEFAULT,
home,
argv,
None,
GLib.SpawnFlags.DO_NOT_REAP_CHILD,
callback,
None,
)
except BaseException as e:
print('==Spawn2 Error==',e)
return False
def on_scroll_event(self, term, scroll_event):
TERMINAL_MIN_OPACITY = 0.2
if (scroll_event.state & Gdk.ModifierType.CONTROL_MASK) != 0 and (scroll_event.state & Gdk.ModifierType.SHIFT_MASK) == 0:
try:
old_opacity = term.notebook.opacity
new_opacity = old_opacity
if scroll_event.delta_y < 0:
new_opacity = min(max(old_opacity + 0.01, TERMINAL_MIN_OPACITY), 1)
elif scroll_event.delta_y > 0:
new_opacity = min(max(old_opacity - 0.01, TERMINAL_MIN_OPACITY), 1)
if new_opacity != old_opacity:
term.notebook.update_opacity(new_opacity)
return True
except GLib.KeyFileError as e:
print("Terminal on_scroll: ", e)
return False
def on_key_press_event(self, widget, key_event):
keyevent_name = get_keyevent_name(key_event)
# print(keyevent_name) #打开这里可以看到每次按的是什么键
if keyevent_name == "Ctrl + Shift + c":
self.copy_clipboard() #复制到剪贴板
return True
elif keyevent_name == "Ctrl + Shift + v":
self.paste_clipboard() #从剪贴板黏贴
return True
elif keyevent_name == "Ctrl + t":
self.notebook.new_term() #开一个新的tab
return True
elif keyevent_name == "Ctrl + w":
self.notebook.close_term(self) #关闭当前tab,如果是最后一个,则退出应用
return True
elif keyevent_name == "Ctrl + Page_Down":
self.notebook.next_term() #切换到下一个Tab
return True
elif keyevent_name == "Ctrl + Page_Up":
self.notebook.previous_term() #切换到前一个Tab
return True
elif keyevent_name == "Ctrl + Alt + Page_Down":
self.notebook.move_to_next() #将Tab右移一个位置
return True
elif keyevent_name == "Ctrl + Alt + Page_Up":
self.notebook.move_to_previous() #将Tab左移一个位置
return True
elif keyevent_name == "Ctrl + Alt + \\":
self.popup_menu() #弹出右键菜单(包含有模板子菜单)
return True
elif keyevent_name == "Ctrl + Alt + /":
self.popup_template_menu() #仅仅弹出模板菜单
return True
elif keyevent_name == "Ctrl + Alt + Up":
self.notebook.increase_opacity() #增加透明度
return True
elif keyevent_name == "Ctrl + Alt + Down":
self.notebook.decrease_opacity() #降低透明度
return True
elif keyevent_name == "Alt + f":
self.notebook.window.do_max(None)
return True
elif keyevent_name == "Alt + Left":
self.resize_window(-20,0)
return True
elif keyevent_name == "Alt + Right":
self.resize_window(20,0)
return True
elif keyevent_name == "Alt + Up":
self.resize_window(0,-20)
return True
elif keyevent_name == "Alt + Down":
self.resize_window(0,20)
return True
elif keyevent_name == "F2":
self.notebook.rename_term()
return True
def resize_window(self,delta_width, delta_height):
win = self.notebook.window
(w,h) = win.get_size()
win.resize(w+delta_width, h+delta_height)
def on_child_exited(self,widget,event):
self.notebook.close_term(self) #关闭当前tab
def on_contents_changed(self, widget):
(text,attr) = widget.get_text()
# print(self.title + ' content changed:\r\n', text)
def on_window_title_changed(self, widget):
self.rename(self.shorten_title(self.get_window_title()))
#简化窗口标题的路径
def shorten_title(self, title):
#将 tony@tonybook:/data/work/gitee/JingTerm
#简化成:~ok:d~w~g~JingTerm
try:
if len(title)<20:
return title
host=""
if ':' in title:
i = title.index(':')
a = title[max(0,i-2):i]
host = a+':'
b = title[i+1:]
else:
b = title
i=b.rindex('/')
arr = [x[0] for x in b.split('/')[1:-1] ]
tail = b[i+1:]
arr.append( tail[0: min(8,len(tail)) ] )
return host + '~'.join(arr)
except:
return title
def on_selection_changed(self, widget):
#linux下面,如果选中了,复制到主选区(可以通过鼠标中间黏贴)
if widget.get_has_selection():
widget.copy_primary()
def clipboard_has_context(self):
clipboard_text = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD).wait_for_text()
return clipboard_text != None and clipboard_text.strip() != ""
def on_button_press(self, widget, event):
if event.button==Gdk.BUTTON_PRIMARY :
# 先聚焦窗口
self.grab_focus()
# 检查是否有可点击的链接
(uri,tag) = self.match_check_event(event)
modifiers = Gtk.accelerator_get_default_mod_mask()
if (event.state & modifiers) == Gdk.ModifierType.CONTROL_MASK and uri != None:
try:
Gtk.show_uri(None, uri, Gtk.get_current_event_time())
return True
except GLib.Error as error:
try :
uri = "http://{}".format(uri)
Gtk.show_uri(None, uri, Gtk.get_current_event_time())
except GLib.Error as error:
print("Could Not Open link", error)
elif event.button==Gdk.BUTTON_SECONDARY:
self.popup_menu()
def popup_menu(self):
menu = self.create_menus()
menu.show_all()
menu.popup_at_pointer()
def popup_template_menu(self):
menu = self.create_template_menus()
menu.show_all()
menu.popup_at_pointer()
def create_template_menus(self):
menu = Gtk.Menu()
cm = ContextMenu(menu, self)
menu.show_all()
menu.popup_at_pointer()
return menu
def create_menus(self):
menu = Gtk.Menu()
if self.get_has_selection():
self.menu_copy = Gtk.MenuItem("Copy")
self.menu_copy.connect("activate", self.menu_clicked)
menu.add(self.menu_copy)
self.menu_reset = Gtk.MenuItem("Reset")
self.menu_reset.connect("activate", self.menu_clicked)
if self.clipboard_has_context():
self.menu_paste = Gtk.MenuItem("Paste")
self.menu_paste.connect("activate", self.menu_clicked)
self.menu_paste_primary = Gtk.MenuItem("Paste Primary")
self.menu_paste_primary.connect("activate", self.menu_clicked)
self.menu_rename = Gtk.MenuItem("Rename")
self.menu_rename.connect("activate", self.menu_clicked)
menu.add(self.menu_reset)
if hasattr(self,'menu_paste'):
menu.add(self.menu_paste)
menu.add(self.menu_paste_primary)
menu.add(self.menu_rename)
self.menu_scroll = Gtk.CheckMenuItem('Scroll on output')
self.menu_scroll.connect("activate", self.menu_clicked)
self.menu_scroll.props.active = self.props.scroll_on_output
menu.add(self.menu_scroll)
menu_template_list = Gtk.Menu()
menu_template = Gtk.MenuItem("Templates")
ContextMenu(menu_template_list, self)
menu_template.set_submenu(menu_template_list)
menu.add(menu_template)
return menu
def menu_clicked(self, menuitem):
if menuitem==self.menu_reset:
self.reset(True,True)
self.feed_child('\r'.encode('utf-8'))
elif hasattr(self,'menu_paste') and menuitem==self.menu_paste:
self.paste_clipboard()
elif menuitem==self.menu_paste_primary:
self.paste_primary ()
elif menuitem==self.menu_rename:
self.notebook.rename_term()
elif hasattr(self,'menu_copy') and menuitem==self.menu_copy:
self.copy_clipboard()
elif menuitem==self.menu_scroll:
self.props.scroll_on_output = not self.props.scroll_on_output
#获取当前目录
def get_cwd(self):
pty = self.get_pty()
if pty :
pty_fd = pty.props.fd
fpid = os.tcgetpgrp(pty_fd)
if fpid > 0:
current_dir = os.readlink("/proc/{}/cwd".format(fpid))
return current_dir
return None
#自动向vte输入内容
def enter(self,content):
self.feed_child(content.encode('utf-8'))
#自动向vte输入内容并回车
def enterln(self,content):
self.feed_child((content+"\r").encode('utf-8'))
#顺序执行命令
def run_commands(self,contents):
ENTERLN='enterln:'
ENTER='enter:'
SLEEP='sleep:'
TITLE='title:'
for i in range(0,len(contents)):
c = contents[i]
if c.startswith(ENTERLN):
self.enterln(c[len(ENTERLN):])
elif c.startswith(ENTER):
self.enterln(c[len(ENTER):])
elif c.startswith(TITLE):
self.rename(c[len(TITLE):],True)
elif c.startswith(SLEEP):
time.sleep(float(c[len(SLEEP):]))
#启用独立线程,执行一批命令,以免互相干扰
def start_commands_thread(self, commands):
def _f():
self.run_commands(commands)
threading.Thread(target=_f).start()
#设置vte中的可以点击的正则表达式
def clickable(self,str_arr):
for exp in str_arr:
try:
regex = Vte.Regex.for_match(exp, -1, 0x00000400) # /* PCRE2_MULTILINE */
id = self.match_add_regex(regex, 0)
self.match_set_cursor_type(id, Gdk.CursorType.HAND2)
except BaseException as e:
try:
regex = GLib.Regex(exp,
GLib.RegexCompileFlags.OPTIMIZE |
GLib.RegexCompileFlags.MULTILINE,
0)
id = self.match_add_gregex(regex, 0)
self.match_set_cursor_type(id, Gdk.CursorType.HAND2)
except BaseException as e:
print(e)
Python
1
https://gitee.com/ryvius_key/JingTerm.git
git@gitee.com:ryvius_key/JingTerm.git
ryvius_key
JingTerm
JingTerm
master

搜索帮助