Implement clicking on URLs to open them
This commit is contained in:
parent
a05b64f2fe
commit
63f8fd5929
@ -9,7 +9,7 @@ from threading import Lock
|
|||||||
|
|
||||||
from .config import build_ansi_color_table
|
from .config import build_ansi_color_table
|
||||||
from .constants import tab_manager, viewport_size, cell_size, ScreenGeometry, GLuint
|
from .constants import tab_manager, viewport_size, cell_size, ScreenGeometry, GLuint
|
||||||
from .utils import get_logical_dpi, to_color, set_primary_selection
|
from .utils import get_logical_dpi, to_color, set_primary_selection, open_url
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
glUniform2ui, glUniform4f, glUniform1i, glUniform2f, glDrawArraysInstanced,
|
glUniform2ui, glUniform4f, glUniform1i, glUniform2f, glDrawArraysInstanced,
|
||||||
GL_TRIANGLE_FAN, glEnable, glDisable, GL_BLEND, glDrawArrays, ColorProfile,
|
GL_TRIANGLE_FAN, glEnable, glDisable, GL_BLEND, glDrawArrays, ColorProfile,
|
||||||
@ -169,6 +169,8 @@ class Selection:
|
|||||||
|
|
||||||
class CharGrid:
|
class CharGrid:
|
||||||
|
|
||||||
|
url_pat = re.compile('(?:http|https|file|ftp)://\S+', re.IGNORECASE)
|
||||||
|
|
||||||
def __init__(self, screen, opts):
|
def __init__(self, screen, opts):
|
||||||
self.buffer_lock = Lock()
|
self.buffer_lock = Lock()
|
||||||
self.current_selection = Selection()
|
self.current_selection = Selection()
|
||||||
@ -283,6 +285,27 @@ class CharGrid:
|
|||||||
if ps and ps.strip():
|
if ps and ps.strip():
|
||||||
set_primary_selection(ps)
|
set_primary_selection(ps)
|
||||||
|
|
||||||
|
def has_url_at(self, x, y):
|
||||||
|
x, y = self.cell_for_pos(x, y)
|
||||||
|
l = self.screen_line(y)
|
||||||
|
if l is not None:
|
||||||
|
text = l.as_base_text()
|
||||||
|
for m in self.url_pat.finditer(text):
|
||||||
|
if m.start() <= x < m.end():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def click_url(self, x, y):
|
||||||
|
x, y = self.cell_for_pos(x, y)
|
||||||
|
l = self.screen_line(y)
|
||||||
|
if l is not None:
|
||||||
|
text = l.as_base_text()
|
||||||
|
for m in self.url_pat.finditer(text):
|
||||||
|
if m.start() <= x < m.end():
|
||||||
|
url = ''.join(l[i] for i in range(*m.span()))
|
||||||
|
if url:
|
||||||
|
open_url(url, self.opts.open_url_with)
|
||||||
|
|
||||||
def screen_line(self, y):
|
def screen_line(self, y):
|
||||||
' Return the Line object corresponding to the yth line on the rendered screen '
|
' Return the Line object corresponding to the yth line on the rendered screen '
|
||||||
if y >= 0 and y < self.screen.lines:
|
if y >= 0 and y < self.screen.lines:
|
||||||
|
|||||||
@ -38,11 +38,45 @@ def to_opacity(x):
|
|||||||
return max(0.3, min(float(x), 1))
|
return max(0.3, min(float(x), 1))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_mods(parts):
|
||||||
|
|
||||||
|
def map_mod(m):
|
||||||
|
return {'CTRL': 'CONTROL', 'CMD': 'CONTROL'}.get(m, m)
|
||||||
|
|
||||||
|
mods = 0
|
||||||
|
for m in parts:
|
||||||
|
try:
|
||||||
|
mods |= getattr(defines, 'GLFW_MOD_' + map_mod(m.upper()))
|
||||||
|
except AttributeError:
|
||||||
|
print('Shortcut: {} has an unknown modifier, ignoring'.format(parts.join('+')), file=sys.stderr)
|
||||||
|
return
|
||||||
|
|
||||||
|
return mods
|
||||||
|
|
||||||
|
|
||||||
|
def parse_key(val, keymap):
|
||||||
|
sc, action = val.partition(' ')[::2]
|
||||||
|
if not sc or not action:
|
||||||
|
return
|
||||||
|
parts = sc.split('+')
|
||||||
|
mods = parse_mods(parts[:-1])
|
||||||
|
key = getattr(defines, 'GLFW_KEY_' + parts[-1].upper(), None)
|
||||||
|
if key is None:
|
||||||
|
print('Shortcut: {} has an unknown key, ignoring'.format(val), file=sys.stderr)
|
||||||
|
return
|
||||||
|
keymap[(mods, key)] = action
|
||||||
|
|
||||||
|
|
||||||
|
def to_open_url_modifiers(val):
|
||||||
|
return parse_mods(val.split('+'))
|
||||||
|
|
||||||
|
|
||||||
type_map = {
|
type_map = {
|
||||||
'scrollback_lines': int,
|
'scrollback_lines': int,
|
||||||
'font_size': to_font_size,
|
'font_size': to_font_size,
|
||||||
'cursor_shape': to_cursor_shape,
|
'cursor_shape': to_cursor_shape,
|
||||||
'cursor_opacity': to_opacity,
|
'cursor_opacity': to_opacity,
|
||||||
|
'open_url_modifiers': to_open_url_modifiers,
|
||||||
'repaint_delay': int,
|
'repaint_delay': int,
|
||||||
'window_border_width': float,
|
'window_border_width': float,
|
||||||
'wheel_scroll_multiplier': float,
|
'wheel_scroll_multiplier': float,
|
||||||
@ -58,30 +92,6 @@ for i in range(16):
|
|||||||
type_map['color%d' % i] = lambda x: to_color(x, validate=True)
|
type_map['color%d' % i] = lambda x: to_color(x, validate=True)
|
||||||
|
|
||||||
|
|
||||||
def parse_key(val, keymap):
|
|
||||||
sc, action = val.partition(' ')[::2]
|
|
||||||
if not sc or not action:
|
|
||||||
return
|
|
||||||
parts = sc.split('+')
|
|
||||||
|
|
||||||
def map_mod(m):
|
|
||||||
return {'CTRL': 'CONTROL', 'CMD': 'CONTROL'}.get(m, m)
|
|
||||||
|
|
||||||
mods = 0
|
|
||||||
for m in parts[:-1]:
|
|
||||||
try:
|
|
||||||
mods |= getattr(defines, 'GLFW_MOD_' + map_mod(m.upper()))
|
|
||||||
except AttributeError:
|
|
||||||
print('Shortcut: {} has an unknown modifier, ignoring'.format(val), file=sys.stderr)
|
|
||||||
return
|
|
||||||
|
|
||||||
key = getattr(defines, 'GLFW_KEY_' + parts[-1].upper(), None)
|
|
||||||
if key is None:
|
|
||||||
print('Shortcut: {} has an unknown key, ignoring'.format(val), file=sys.stderr)
|
|
||||||
return
|
|
||||||
keymap[(mods, key)] = action
|
|
||||||
|
|
||||||
|
|
||||||
def parse_config(lines):
|
def parse_config(lines):
|
||||||
ans = {'keymap': {}}
|
ans = {'keymap': {}}
|
||||||
for line in lines:
|
for line in lines:
|
||||||
|
|||||||
@ -1,13 +1,20 @@
|
|||||||
# vim:fileencoding=utf-8:ft=config
|
# vim:fileencoding=utf-8:ft=config
|
||||||
|
|
||||||
# The value of the TERM environment variable to set
|
# Font family
|
||||||
term xterm-kitty
|
font_family monospace
|
||||||
|
|
||||||
|
# Font size (in pts)
|
||||||
|
font_size 11.0
|
||||||
|
|
||||||
# The foreground color
|
# The foreground color
|
||||||
foreground #dddddd
|
foreground #dddddd
|
||||||
|
|
||||||
# The background color
|
# The background color
|
||||||
background #000000
|
background #000000
|
||||||
|
|
||||||
# The foreground for selections
|
# The foreground for selections
|
||||||
selection_foreground #000000
|
selection_foreground #000000
|
||||||
|
|
||||||
# The background for selections
|
# The background for selections
|
||||||
selection_background #FFFACD
|
selection_background #FFFACD
|
||||||
|
|
||||||
@ -28,12 +35,6 @@ cursor_blink_interval 0.5
|
|||||||
# zero or a negative number to never stop blinking.
|
# zero or a negative number to never stop blinking.
|
||||||
cursor_stop_blinking_after 15.0
|
cursor_stop_blinking_after 15.0
|
||||||
|
|
||||||
# Font family
|
|
||||||
font_family monospace
|
|
||||||
|
|
||||||
# Font size (in pts)
|
|
||||||
font_size 11.0
|
|
||||||
|
|
||||||
# Number of lines of history to keep in memory for scrolling back
|
# Number of lines of history to keep in memory for scrolling back
|
||||||
scrollback_lines 2000
|
scrollback_lines 2000
|
||||||
|
|
||||||
@ -52,6 +53,16 @@ repaint_delay 20
|
|||||||
# zero or a negative number to disable mouse cursor hiding.
|
# zero or a negative number to disable mouse cursor hiding.
|
||||||
mouse_hide_wait 3.0
|
mouse_hide_wait 3.0
|
||||||
|
|
||||||
|
# The modifier keys to press when clicking with the mouse on URLs to open the URL
|
||||||
|
open_url_modifiers ctrl+shift
|
||||||
|
|
||||||
|
# The program with which to open URLs that are clicked on. The special value "default" means to
|
||||||
|
# use the operating system's default URL handler.
|
||||||
|
open_url_with default
|
||||||
|
|
||||||
|
# The value of the TERM environment variable to set
|
||||||
|
term xterm-kitty
|
||||||
|
|
||||||
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
||||||
window_border_width 2
|
window_border_width 2
|
||||||
|
|
||||||
@ -61,6 +72,9 @@ active_border_color #00ff00
|
|||||||
# The color for the border of inactive windows
|
# The color for the border of inactive windows
|
||||||
inactive_border_color #cccccc
|
inactive_border_color #cccccc
|
||||||
|
|
||||||
|
# The 16 terminal colors. There are 8 basic colors, each color has a dull and
|
||||||
|
# bright version.
|
||||||
|
|
||||||
# black
|
# black
|
||||||
color0 #000000
|
color0 #000000
|
||||||
color8 #4d4d4d
|
color8 #4d4d4d
|
||||||
|
|||||||
@ -19,7 +19,7 @@ from .constants import viewport_size, shell_path, appname, set_tab_manager, tab_
|
|||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
glViewport, glBlendFunc, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GLFW_PRESS,
|
glViewport, glBlendFunc, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GLFW_PRESS,
|
||||||
GLFW_REPEAT, GLFW_MOUSE_BUTTON_1, glfw_post_empty_event,
|
GLFW_REPEAT, GLFW_MOUSE_BUTTON_1, glfw_post_empty_event,
|
||||||
GLFW_CURSOR_NORMAL, GLFW_CURSOR, GLFW_CURSOR_HIDDEN
|
GLFW_CURSOR_NORMAL, GLFW_CURSOR, GLFW_CURSOR_HIDDEN,
|
||||||
)
|
)
|
||||||
from .fonts import set_font_family
|
from .fonts import set_font_family
|
||||||
from .borders import Borders, BordersProgram
|
from .borders import Borders, BordersProgram
|
||||||
@ -385,6 +385,9 @@ class TabManager(Thread):
|
|||||||
def hide_mouse_cursor(self):
|
def hide_mouse_cursor(self):
|
||||||
self.glfw_window.set_input_mode(GLFW_CURSOR, GLFW_CURSOR_HIDDEN)
|
self.glfw_window.set_input_mode(GLFW_CURSOR, GLFW_CURSOR_HIDDEN)
|
||||||
|
|
||||||
|
def change_mouse_cursor(self, click=False):
|
||||||
|
self.glfw_window.set_click_cursor(click)
|
||||||
|
|
||||||
def start_cursor_blink(self):
|
def start_cursor_blink(self):
|
||||||
self.cursor_blinking = True
|
self.cursor_blinking = True
|
||||||
if self.opts.cursor_stop_blinking_after > 0:
|
if self.opts.cursor_stop_blinking_after > 0:
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
import ctypes
|
import ctypes
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
@ -270,3 +271,12 @@ def set_primary_selection(text):
|
|||||||
p = subprocess.Popen(['xsel', '-i', '-p'], stdin=subprocess.PIPE)
|
p = subprocess.Popen(['xsel', '-i', '-p'], stdin=subprocess.PIPE)
|
||||||
p.stdin.write(text), p.stdin.close()
|
p.stdin.write(text), p.stdin.close()
|
||||||
p.wait()
|
p.wait()
|
||||||
|
|
||||||
|
|
||||||
|
def open_url(url, program='default'):
|
||||||
|
if program == 'default':
|
||||||
|
cmd = ['xdg-open']
|
||||||
|
else:
|
||||||
|
cmd = shlex.split(program)
|
||||||
|
cmd.append(url)
|
||||||
|
subprocess.Popen(cmd).wait()
|
||||||
|
|||||||
@ -156,6 +156,8 @@ class Window:
|
|||||||
x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top)
|
x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top)
|
||||||
self.char_grid.update_drag(action == GLFW_PRESS, x, y)
|
self.char_grid.update_drag(action == GLFW_PRESS, x, y)
|
||||||
if action == GLFW_RELEASE:
|
if action == GLFW_RELEASE:
|
||||||
|
if mods == self.char_grid.opts.open_url_modifiers:
|
||||||
|
self.char_grid.click_url(x, y)
|
||||||
self.click_queue.append(monotonic())
|
self.click_queue.append(monotonic())
|
||||||
self.dispatch_multi_click(x, y)
|
self.dispatch_multi_click(x, y)
|
||||||
elif button == GLFW_MOUSE_BUTTON_MIDDLE:
|
elif button == GLFW_MOUSE_BUTTON_MIDDLE:
|
||||||
@ -167,8 +169,11 @@ class Window:
|
|||||||
x, y = self.char_grid.cell_for_pos(x, y)
|
x, y = self.char_grid.cell_for_pos(x, y)
|
||||||
|
|
||||||
def on_mouse_move(self, window, x, y):
|
def on_mouse_move(self, window, x, y):
|
||||||
|
x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top)
|
||||||
if self.char_grid.current_selection.in_progress:
|
if self.char_grid.current_selection.in_progress:
|
||||||
self.char_grid.update_drag(None, max(0, x - self.geometry.left), max(0, y - self.geometry.top))
|
self.char_grid.update_drag(None, x, y)
|
||||||
|
tm = tab_manager()
|
||||||
|
tm.queue_ui_action(tab_manager().change_mouse_cursor, self.char_grid.has_url_at(x, y))
|
||||||
|
|
||||||
def on_mouse_scroll(self, window, x, y):
|
def on_mouse_scroll(self, window, x, y):
|
||||||
handle_event = (
|
handle_event = (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user