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 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
|
#!/usr/bin/env python3
"""
Syncthing-GTK - DaemonSettingsDialog
Universal dialog handler for all Syncthing settings and editing
"""
from gi.repository import Gtk
from syncthing_gtk.tools import (
IS_UNITY, IS_GNOME, IS_WINDOWS,
set_run_on_startup, is_ran_on_startup, get_config_dir, get_executable
)
from syncthing_gtk.notifications import Notifications, HAS_DESKTOP_NOTIFY
from syncthing_gtk.editordialog import EditorDialog
from syncthing_gtk.tools import _ # gettext function
from syncthing_gtk.configuration import LONG_AGO
try:
from syncthing_gtk.stdownloader import StDownloader
except ImportError:
StDownloader = None
import os, logging
log = logging.getLogger("UISettingsDialog")
VALUES = [ "vautostart_daemon", "vautokill_daemon", "vminimize_on_start",
"vautostart", "vuse_old_header", "vicons_in_menu",
"vforce_dark_theme", "vdaemon_priority", "vfolder_as_path",
"vnotification_for_update", "vnotification_for_folder",
"vnotification_for_error", "vst_autoupdate", "vsyncthing_binary",
"vsyncthing_arguments", "vmax_cpus", "vicon_theme", "vlanguage"
]
# Values for filemanager integration. Key is ID of checkbox widget
FM_DATA = {
"fmcb_nemo" : (
"nemo/extensions-3.0/libnemo-python.so", # python plugin location, relative to /usr/lib
"Nemo python bindings", # name or description of required package
"syncthing-plugin-nemo", # plugin script filename, without extension
"nemo-python/extensions", # script folder, relative to XDG_DATA_HOME
"Nemo" # name
),
"fmcb_nautilus" : (
"nautilus/extensions-3.0/libnautilus-python.so",
"Nautilus python bindings",
"syncthing-plugin-nautilus",
"nautilus-python/extensions",
"Nautilus"
),
"fmcb_caja" : (
"caja/extensions-2.0/libcaja-python.so",
"Caja python bindings",
"syncthing-plugin-caja",
"caja-python/extensions",
"Caja"
)
}
class UISettingsDialog(EditorDialog):
SETTING_NEEDS_RESTART = [
"vuse_old_header", "vforce_dark_theme", "vicons_in_menu",
"vicon_theme", "vlanguage"
]
def __init__(self, app):
EditorDialog.__init__(self, app, "ui-settings.glade",
_("UI Settings"))
self.app = app
def run(self):
return self["dialog"].run()
def cb_btBrowse_clicked(self, *a):
""" Display file browser dialog to browse for syncthing binary """
browse_for_binary(self["editor"], self, "vsyncthing_binary")
def cb_vmax_cpus_value_changed(self, sb):
if sb.get_adjustment().get_value() == 0:
sb.set_text(_("Unlimited"))
#@Overrides
def load_data(self):
# Don't load data from syncthing daemon, it knows nothing...
copy = { k : self.app.config[k] for k in self.app.config }
if IS_UNITY or IS_GNOME:
self["vuse_old_header"].set_visible(False)
self["vuse_old_header"].set_no_show_all(True)
self["vicons_in_menu"].set_visible(False)
self["vicons_in_menu"].set_no_show_all(True)
if IS_GNOME:
self["vautostart"].set_visible(False)
self["vautostart"].set_no_show_all(True)
if not HAS_DESKTOP_NOTIFY:
# Disable notifications settings if required
# library is not available
self["lblNotifications"].set_sensitive(False)
self["vnotification_for_update"].set_sensitive(False)
self["vnotification_for_folder"].set_sensitive(False)
self["vnotification_for_error"].set_sensitive(False)
if IS_WINDOWS:
# Leave daemon running causes weird bugs on Windows,
# so only one option is enabled there
self["rbOnExitLeave"].set_sensitive(False)
self["rbOnExitAsk"].set_sensitive(False)
self["rbOnExitTerminate"].set_active(True)
self["vforce_dark_theme"].set_visible(True)
self["lbl_vlanguage"].set_visible(True)
self["vlanguage"].set_visible(True)
# Check for filemanager python bindings current state of plugins
status = []
for widget_id in FM_DATA:
so_file, package, plugin, location, name = FM_DATA[widget_id]
if not get_fm_source_path(plugin) is None:
if library_exists(so_file):
self[widget_id].set_sensitive(True)
self[widget_id].set_active(
os.path.exists(get_fm_target_path(plugin, location))
)
else:
log.warning("Cannot find %s required to support %s", so_file, name)
status.append(_("Install %(package)s package to enable %(feature)s support") % {
'package' : package,
'feature' : name
})
else:
log.warning("Cannot find %s.py required to support %s", plugin, name)
self["fmLblIntegrationStatus"].set_text("\n".join(status))
if StDownloader is None:
for name in ("vst_autoupdate", "lblAutoupdate", "lblsyncthing_binary",
"lblsyncthing_binary2", "vsyncthing_binary", "btBrowse"):
self[name].set_visible(False)
self.cb_data_loaded(copy)
self.cb_check_value()
#@Overrides
def display_value(self, key, w):
if key == "vautostart_daemon":
value = self.get_value(key[1:])
if value == 0: self["rbOnStartWait"].set_active(True)
elif value == 1: self["rbOnStartRun"].set_active(True)
else: self["rbOnStartAsk"].set_active(True)
elif key == "vautokill_daemon":
value = self.get_value(key[1:])
if value == 1: self["rbOnExitTerminate"].set_active(True)
elif value == 0: self["rbOnExitLeave"].set_active(True)
else: self["rbOnExitAsk"].set_active(True)
else:
return EditorDialog.display_value(self, key, w)
#@Overrides
def store_value(self, key, w):
if key == "vautostart_daemon":
if self["rbOnStartWait"].get_active() : self.set_value(key[1:], 0)
elif self["rbOnStartRun"].get_active() : self.set_value(key[1:], 1)
else: return self.set_value(key[1:], 2) # vOnStartAsk
elif key == "vautokill_daemon":
if self["rbOnExitTerminate"].get_active() : return self.set_value(key[1:], 1)
elif self["rbOnExitLeave"].get_active() : return self.set_value(key[1:], 0)
else: return self.set_value(key[1:], 2) # vOnExitAsk
elif key == "vst_autoupdate":
# Reset updatecheck timer when autoupdate is turned on
if self["vst_autoupdate"].get_active():
self.values["last_updatecheck"] = LONG_AGO
return EditorDialog.store_value(self, key, w)
else:
return EditorDialog.store_value(self, key, w)
#@Overrides
def set_value(self, key, value):
if key == "autostart":
set_run_on_startup(value, "Syncthing-GTK", get_executable(),
"/usr/share/syncthing-gtk/icons/st-logo-128.png",
"GUI for Syncthing")
elif key == "daemon_priority":
return EditorDialog.set_value(self, key, int(value))
else:
return EditorDialog.set_value(self, key, value)
#@Overrides
def get_value(self, key):
if key == "autostart":
return is_ran_on_startup("Syncthing-GTK")
else:
return EditorDialog.get_value(self, key)
#@Overrides
def on_data_loaded(self):
self.values = self.config
self.checks = {
"vsyncthing_binary" : lambda p : os.path.isfile(p) and os.access(p, os.X_OK)
}
return self.display_values(VALUES)
#@Overrides
def update_special_widgets(self, *a):
pass
#@Overrides
def on_save_requested(self):
self.store_values(VALUES)
# Save data to configuration file
for k in self.values:
self.app.config[k] = self.values[k]
# Create / delete fm integration scripts
for widget_id in FM_DATA:
so_file, package, plugin, location, name = FM_DATA[widget_id]
if self[widget_id].get_sensitive() and self[widget_id].get_active():
# Should be enabled. Check if script is in place and create it if not
source = get_fm_source_path(plugin)
target = get_fm_target_path(plugin, location)
if not source is None and not os.path.exists(target):
try:
# Create directory first
os.makedirs(os.path.dirname(target))
except Exception as e:
# Ignore "file already exists" error
pass
try:
if is_file_or_symlink(target):
os.unlink(target)
os.symlink(source, target)
log.info("Created symlink '%s' -> '%s'", source, target)
except Exception as e:
log.error("Failed to symlink '%s' -> '%s'", source, target)
log.error(e)
else:
# Should be disabled. Remove redundant scripts
for extension in ("py", "pyc", "pyo"):
target = get_fm_target_path(plugin, location, extension)
if is_file_or_symlink(target):
try:
os.unlink(target)
log.info("Removed '%s'", target)
except Exception as e:
log.error("Failed to remove '%s'", target)
log.error(e)
# Report work done
self.syncthing_cb_post_config()
#@Overrides
def on_saved(self):
self.close()
# Recreate Notifications object if needed
if HAS_DESKTOP_NOTIFY:
if not self.app.notifications is None:
self.app.notifications.kill()
self.app.notifications = None
if self.app.config["notification_for_update"] or self.app.config["notification_for_error"]:
self.app.notifications = Notifications(self.app, self.app.daemon)
# Restart or cancel updatecheck
self.app.check_for_upgrade()
# Update directory boxes to reflect change in 'folder_as_path'
for rid in self.app.folders:
box = self.app.folders[rid]
title = box["path"] if self.app.config["folder_as_path"] else rid
box.set_visible("id", self.app.config["folder_as_path"])
box.set_title(title)
def library_exists(name):
"""
Checks if there is specified so file installed in one of known prefixes
"""
PREFIXES = [
"/usr/lib64", # Fedora
"/usr/lib",
"/usr/local/lib/",
"/usr/x86_64-pc-linux-gnu/lib/",
"/usr/i686-pc-linux-gnu/lib/",
"/usr/lib/x86_64-linux-gnu/",
"/usr/lib/i386-linux-gnu/",
]
for prefix in PREFIXES:
if os.path.exists(os.path.join(prefix, name)):
return True
return False
def get_fm_target_path(plugin, location, extension="py"):
"""
Returns full path to plugin file in filemanager plugins directory
"""
datahome = os.path.expanduser("~/.local/share")
if "XDG_DATA_HOME" in os.environ:
datahome = os.environ["XDG_DATA_HOME"]
return os.path.join(datahome, location, "%s.%s" % (plugin, extension))
def get_fm_source_path(plugin):
"""
Returns path to location where plugin file is installed
"""
filename = "%s.py" % (plugin,)
paths = (
# Relative path used while developing or when running
# ST-GTK without installation
"./scripts/",
# Default installation path
"/usr/share/syncthing-gtk",
# Not-so default installation path
"/usr/local/share/syncthing-gtk",
)
for path in paths:
fn = os.path.abspath(os.path.join(path, filename))
if os.path.exists(fn):
return fn
return None
def is_file_or_symlink(path):
"""
Returns True if specified file exists, even as broken symlink.
(os.path.exists() returns False for broken symlinks)
"""
if os.path.exists(path): return True
try:
os.readlink(path)
return True
except:
pass
return False
def browse_for_binary(parent_window, settings_dialog, value):
"""
Display file browser dialog to browse for syncthing binary.
Used here and by FindDaemonDialog as well.
"""
# Prepare dialog
d = Gtk.FileChooserDialog(
_("Browse for Syncthing binary"),
parent_window,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
# Prepare filter
f = Gtk.FileFilter()
if IS_WINDOWS:
f.set_name("Executables")
f.add_pattern("*.exe")
else:
f.set_name("Binaries")
f.add_mime_type("application/x-executable")
f.add_mime_type("application/x-shellscript")
d.add_filter(f)
# Set default path
confdir = os.path.join(get_config_dir(), "syncthing")
prevvalue = str(settings_dialog[value].get_text()).strip()
if prevvalue and os.path.exists(os.path.split(prevvalue)[0]):
d.set_current_folder(os.path.split(prevvalue)[0])
elif os.path.exists(confdir):
d.set_current_folder(confdir)
elif IS_WINDOWS:
if "CommonProgramFiles" in os.environ:
d.set_current_folder(os.environ["CommonProgramFiles"])
elif os.path.exists("C:\\Program Files"):
d.set_current_folder("C:\\Program Files")
# Else nothing, just start whatever you like
else:
d.set_current_folder("/usr/bin")
# Get response
if d.run() == Gtk.ResponseType.OK:
settings_dialog[value].set_text(d.get_filename())
d.destroy()
|