-
Notifications
You must be signed in to change notification settings - Fork 15
/
nautilus-copypath.py
220 lines (173 loc) · 7.44 KB
/
nautilus-copypath.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# ----------------------------------------------------------------------------------------
# nautilus-copypath - Quickly copy file paths to the clipboard from Nautilus.
# Copyright (C) Ronen Lapushner 2017-2023.
# Copyright (C) Fynn Freyer 2023.
# Distributed under the GPL-v3+ license. See LICENSE for more information
# ----------------------------------------------------------------------------------------
from gi.repository import Nautilus, GObject, Gdk, Gtk
import os
from platform import system
import gi
# Import the correct GI version
gi_version_major = 3 if 30 <= gi.version_info[1] < 40 else 4
gi.require_versions({
'Nautilus': '3.0' if gi_version_major == 3 else '4.0',
'Gdk': '3.0' if gi_version_major == 3 else '4.0',
'Gtk': '3.0' if gi_version_major == 3 else '4.0'
})
class CopyPathExtensionSettings:
"""
Configuration object for the nautilus-copypath extension.
Can be automatically populated from ``NAUTILUS_COPYPATH_*`` environment variables.
"""
@staticmethod
def __cast_env_var(name, default=None):
"""
Try to cast the value of ${name} to a python object.
:param name: The name of the environment variable. E.g., "``NAUTILUS_COPYPATH_WINPATH``".
:param default: Optionally, a default value if the environment variable is not set. Standard is ``None``.
:return: The value of the environment variable. Will be cast to bool for integers and certain strings.
"""
value = os.environ.get(name, default)
# define a mapping for common boolean keywords
cast_map = {
'true': True,
'yes': True,
'y': True,
'false': False,
'no': False,
'n': False,
}
# if the env var is defined, i.e. different from the default
if value != default:
# we try two different casts to boolean
# first we cast to bool via int, if this fails,
# secondly we fall back to our cast map,
# otherwise just return the string
try:
value = bool(int(value))
except ValueError:
try:
value = cast_map[value.lower()]
except KeyError:
pass
return value
def __init__(self):
is_windows = system() == 'Windows'
self.winpath = self.__cast_env_var(
'NAUTILUS_COPYPATH_WINPATH', default=is_windows)
self.sanitize_paths = self.__cast_env_var(
'NAUTILUS_COPYPATH_SANITIZE_PATHS', default=True)
self.quote_paths = self.__cast_env_var(
'NAUTILUS_COPYPATH_QUOTE_PATHS', default=False)
# use system default for line breaks
line_break = '\r\n' if is_windows else '\n'
# we want to avoid casting to bool here, so we take the value from env directly
path_separator = os.environ.get(
'NAUTILUS_COPYPATH_PATH_SEPARATOR', line_break)
# enable using os.pathsep
self.path_separator = os.pathsep if path_separator == 'os.pathsep' else path_separator
winpath = False
"""
Whether to assume Windows-style paths. Default is determined by result of ``platform.system()``.
Controlled by the ``NAUTILUS_COPYPATH_WINPATH`` environment variable.
"""
sanitize_paths = True
"""
Whether to escape paths. Defaults to true.
Controlled by the ``NAUTILUS_COPYPATH_SANITIZE_PATHS`` environment variable.
"""
quote_paths = False
"""
Whether to surround paths with quotes. Defaults to false.
Controlled by the ``NAUTILUS_COPYPATH_QUOTE_PATHS`` environment variable.
"""
path_separator = ''
r"""
The symbol to use for separating multiple copied paths.
Defaults to LF (line feed) on *nix and CRLF on Windows.
Another possible value is ``os.pathsep`` to use the default path separator for the system.
Controlled by ``NAUTILUS_COPYPATH_PATH_SEPARATOR``.
"""
class CopyPathExtension(GObject.GObject, Nautilus.MenuProvider):
def __init__(self):
# Initialize clipboard
if gi_version_major == 4:
self.clipboard = Gdk.Display.get_default().get_clipboard()
else:
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
self.config = CopyPathExtensionSettings()
# Determine appropriate sanitization function
self.__sanitize_path = self.__sanitize_nix_path
if self.config.winpath:
self.__sanitize_path = self.__sanitize_win_path
def __transform_paths(self, paths):
"""Modify paths based on config values and transform them into a string."""
# Apply sanitization if requested
if self.config.sanitize_paths:
paths = [self.__sanitize_path(path) for path in paths]
# Apply quoting if requested
if self.config.quote_paths:
paths = ['"{}"'.format(path) for path in paths]
return paths
@staticmethod
def __sanitize_nix_path(path):
# Replace spaces and parenthesis with their Linux-compatible equivalents.
return path.replace(' ', '\\ ').replace('(', '\\(').replace(')', '\\)')
@staticmethod
def __sanitize_win_path(path):
return path.replace('smb://', '\\\\').replace('/', '\\')
def __copy_files_path(self, menu, files):
pathstr = None
# Get the paths for all the files.
# Also, strip any protocol headers, if required.
# TODO confirm with author:
# windows function doesn't sanitize file names here.
# is this correct? if so this behavior needs to change
# also, this would probably a lot cleaner with pathlib
paths = self.__transform_paths([
fileinfo.get_location().get_path()
for fileinfo in files
])
# Append to the path string
if len(files) > 1:
pathstr = self.config.path_separator.join(paths)
elif len(files) == 1:
pathstr = paths[0]
# Set clipboard text
if pathstr is not None:
if gi_version_major == 4:
self.clipboard.set(pathstr)
else:
self.clipboard.set_text(pathstr, -1)
def __copy_dir_path(self, menu, path):
if path is not None:
pathstr = self.__transform_paths([path.get_location().get_path()])
if gi_version_major == 4:
self.clipboard.set(pathstr)
else:
self.clipboard.set_text(pathstr, -1)
def get_file_items(self, *args, **kwargs):
files = args[0] if gi_version_major == 4 else args[1]
# If there are many items to copy, change the label
# to reflect that.
if len(files) > 1:
item_label = 'Copy Paths'
else:
item_label = 'Copy Path'
item_copy_path = Nautilus.MenuItem(
name='PathUtils::CopyPath',
label=item_label,
tip='Copy the full path to the clipboard'
)
item_copy_path.connect('activate', self.__copy_files_path, files)
return item_copy_path,
def get_background_items(self, *args, **kwargs):
file = args[0] if gi_version_major == 4 else args[1]
item_copy_dir_path = Nautilus.MenuItem(
name='PathUtils::CopyCurrentDirPath',
label='Copy Directory Path',
tip='''Copy the current directory's path to the clipboard'''
)
item_copy_dir_path.connect('activate', self.__copy_dir_path, file)
return item_copy_dir_path,