Commit Diff


commit - 633bf6d87d247b0fddf6259b176f491c3f736318
commit + eb2ed626f304f3f1e00711c20d76ecfd8dcc5ce7
blob - 18eb35022c9575129ab163d736402e4e0dff5bfb
blob + 75f3482e4c2a078554ed59ba188c66b6d06af796
--- README.md
+++ README.md
@@ -120,9 +120,9 @@ that could be helpful to others, consider adding it to
 
 ## User files
 
-Telescope stores user files in `~/.telescope`.  The usage and contents
-of these files are described in [the man page](telescope.1), under
-"FILES".  There's no support yet for XDG-style directories.
+Telescope stores user files according to the
+[XDG Base Directory Specification][xdg]. The usage and contents of these files
+are described in [the man page](telescope.1), under "FILES".
 
 Only one instance of Telescope can be running at time per user.
 
@@ -137,3 +137,4 @@ distributed under the [UNICODE, Inc license agreement]
 
 
 [unicode-license]: https://www.unicode.org/license.html
+[xdg]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
blob - e46a362c97d9a10cb14ba7d4679a8c1e6e47497b
blob + 2c6daa4565ce45e2f7eb957d4e0a447c58baf4e6
--- contrib/README.md
+++ contrib/README.md
@@ -3,7 +3,7 @@
  - `brutalist.config`: a brutalist theme
  - `light.config`: an opinionated theme for light terminals.  Load it
    with `telescope -c contrib/light.config` or copy it to
-   `~/.telescope/config`
+   `~/.config/telescope/config`
  - `dark.config`: an opinionated theme for dark terminals.  Load it
    with `telescope -c contrib/dark.config` or copy it to
-   `~/.telecsope/config`
+   `~/.config/telescope/config`
blob - /dev/null
blob + 79a734da3fd082f4e974a0d403a049261db88678 (mode 755)
--- /dev/null
+++ contrib/xdg-migrate.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+old_path="$HOME/.telescope"
+
+Die() {
+	printf 'error: %s\n' "$1" 1>&2
+	exit 1
+}
+
+[ -e "$old_path" ] || Die "$old_path does not exist."
+[ -d "$old_path" ] || Die "$old_path is not a directory."
+
+xdg_config="${XDG_CONFIG_HOME:-$HOME/.config}/telescope"
+xdg_data="${XDG_DATA_HOME:-$HOME/.local/share}/telescope"
+xdg_cache="${XDG_CACHE_HOME:-$HOME/.cache}/telescope"
+
+mkdir -p "$xdg_config" "$xdg_data" "$xdg_cache"
+
+for filepath in \
+	"$xdg_config/config" \
+	"$xdg_config/pages" \
+	"$xdg_data/bookmarks.gmi" \
+	"$xdg_data/known_hosts"
+do
+	old_file="$old_path/${filepath##*/}"
+	[ -e "$old_file" ] && cp -R "$old_file" "filepath"
+done
+
+printf "\
+WARNING: the old ~/.telescope directory will be removed.
+
+Every file/directory other than the followings has not been copyied:
+    - config
+    - bookmarks.gmi
+    - known_hosts
+    - pages/
+
+Are you sure? [Y/n] "
+
+read -r reply
+case $reply in
+	[yY]) rm -r "$old_path" && printf 'done\n' ;;
+esac
blob - 0a10064c4146364f37a288587015018ce0c4b532
blob + fe5290ef682562a940933715bda1bf34554c84d7
--- fs.c
+++ fs.c
@@ -15,7 +15,7 @@
  */
 
 /*
- * Handles the data in ~/.telescope
+ * Handles config and runtime files
  */
 
 #include "compat.h"
@@ -27,11 +27,13 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <libgen.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
+#include "fs.h"
 #include "pages.h"
 #include "telescope.h"
 
@@ -52,11 +54,25 @@ static void		 handle_session_tab_title(struct imsg*, s
 static void		 handle_session_end(struct imsg*, size_t);
 static void		 handle_dispatch_imsg(int, short, void*);
 static int		 fs_send_ui(int, uint32_t, int, const void *, uint16_t);
+static size_t		 join_path(char*, const char*, const char*, size_t);
+static void		 getenv_default(char*, const char*, const char*, size_t);
+static void		 mkdirs(const char*, mode_t);
+static void		 xdg_init(void);
 
 static struct imsgev		*iev_ui;
 static FILE			*session;
 
-static char	base_path[PATH_MAX];
+/* WARNING: xdg_*_base variables are not initialized if ~/.telescope exists */
+static char	xdg_config_base[PATH_MAX];
+static char	xdg_data_base[PATH_MAX];
+static char	xdg_cache_base[PATH_MAX];
+
+/* *_path_base variables are all equal to $HOME/.telescope if it exists */
+static char	config_path_base[PATH_MAX];
+static char	data_path_base[PATH_MAX];
+static char	cache_path_base[PATH_MAX];
+
+char		config_path[PATH_MAX];
 static char	lockfile_path[PATH_MAX];
 static char	bookmark_file[PATH_MAX];
 static char	known_hosts_file[PATH_MAX], known_hosts_tmp[PATH_MAX];
@@ -140,7 +156,7 @@ handle_get(struct imsg *imsg, size_t datalen)
 	if (page == NULL)
 		goto notfound;
 
-	strlcpy(path, base_path, sizeof(path));
+	strlcpy(path, config_path_base, sizeof(path));
 	strlcat(path, "/", sizeof(path));
 	if (page->path != NULL)
 		strlcat(path, page->path, sizeof(path));
@@ -514,7 +530,7 @@ handle_dispatch_imsg(int fd, short ev, void *d)
 
 	if (dispatch_imsg(iev, ev, handlers, sizeof(handlers)) == -1) {
 		/*
-		 * This should leave a ~/.telescope/crashed file to
+		 * This should leave a ~/.cache/telescope/crashed file to
 		 * trigger about:crash on next run.  Unfortunately, if
 		 * the main process dies the fs sticks around and
 		 * doesn't notice that the fd was closed.  Why EV_READ
@@ -537,32 +553,115 @@ fs_send_ui(int type, uint32_t peerid, int fd, const vo
 	    data, datalen);
 }
 
-int
-fs_init(void)
+static size_t
+join_path(char *buf, const char *lhs, const char *rhs, size_t buflen)
 {
-	strlcpy(base_path, getenv("HOME"), sizeof(base_path));
-	strlcat(base_path, "/.telescope", sizeof(base_path));
-	mkdir(base_path, 0700);
+	strlcpy(buf, lhs, buflen);
+	return strlcat(buf, rhs, buflen);
+}
 
-	strlcpy(lockfile_path, base_path, sizeof(lockfile_path));
-	strlcat(lockfile_path, "/lock", sizeof(lockfile_path));
-
-	strlcpy(bookmark_file, base_path, sizeof(bookmark_file));
-	strlcat(bookmark_file, "/bookmarks.gmi", sizeof(bookmark_file));
+static void
+getenv_default(char *buf, const char *name, const char *def, size_t buflen)
+{
+	size_t ret;
+	char *home, *env;
 
-	strlcpy(known_hosts_file, base_path, sizeof(known_hosts_file));
-	strlcat(known_hosts_file, "/known_hosts", sizeof(known_hosts_file));
+	if ((home = getenv("HOME")) == NULL)
+		errx(1, "HOME is not defined");
 
-	strlcpy(known_hosts_tmp, base_path, sizeof(known_hosts_tmp));
-	strlcat(known_hosts_tmp, "/known_hosts.tmp.XXXXXXXXXX",
-	    sizeof(known_hosts_file));
+	if ((env = getenv(name)) != NULL)
+		ret = strlcpy(buf, env, buflen);
+	else
+		ret = join_path(buf, home, def, buflen);
 
-	strlcpy(session_file, base_path, sizeof(session_file));
-	strlcat(session_file, "/session", sizeof(session_file));
+	if (ret >= buflen)
+		errx(1, "buffer too small for %s", name);
+}
 
-	strlcpy(crashed_file, base_path, sizeof(crashed_file));
-	strlcat(crashed_file, "/crashed", sizeof(crashed_file));
+static void
+mkdirs(const char *path, mode_t mode)
+{
+	char copy[PATH_MAX+1], *parent;
 
+	strlcpy(copy, path, sizeof(copy));
+	parent = dirname(copy);
+	if (!strcmp(parent, "/"))
+		return;
+	mkdirs(parent, mode);
+
+	if (mkdir(path, mode) != 0) {
+		if (errno == EEXIST)
+			return;
+		err(1, "can't mkdir %s", path);
+	}
+}
+
+static void
+xdg_init(void)
+{
+	char *home, old_path[PATH_MAX];
+	struct stat info;
+
+	/* old path */
+	if ((home = getenv("HOME")) == NULL)
+		errx(1, "HOME is not defined");
+	join_path(old_path, home, "/.telescope", sizeof(old_path));
+
+	/* if ~/.telescope exists, use that instead of xdg dirs */
+	if (stat(old_path, &info) == 0 && S_ISDIR(info.st_mode)) {
+		join_path(config_path_base, home, "/.telescope",
+		    sizeof(config_path_base));
+		join_path(data_path_base, home, "/.telescope",
+		    sizeof(data_path_base));
+		join_path(cache_path_base, home, "/.telescope",
+		    sizeof(cache_path_base));
+		return;
+	}
+
+	/* xdg paths */
+	getenv_default(xdg_config_base, "XDG_CONFIG_HOME", "/.config",
+	    sizeof(xdg_config_base));
+	getenv_default(xdg_data_base, "XDG_DATA_HOME", "/.local/share",
+	    sizeof(xdg_data_base));
+	getenv_default(xdg_cache_base, "XDG_CACHE_HOME", "/.cache",
+	    sizeof(xdg_cache_base));
+
+	join_path(config_path_base, xdg_config_base, "/telescope",
+	    sizeof(config_path_base));
+	join_path(data_path_base, xdg_data_base, "/telescope",
+	    sizeof(data_path_base));
+	join_path(cache_path_base, xdg_cache_base, "/telescope",
+	    sizeof(cache_path_base));
+
+	mkdirs(xdg_config_base, S_IRWXU);
+	mkdirs(xdg_data_base, S_IRWXU);
+	mkdirs(xdg_cache_base, S_IRWXU);
+
+	mkdirs(config_path_base, S_IRWXU);
+	mkdirs(data_path_base, S_IRWXU);
+	mkdirs(cache_path_base, S_IRWXU);
+}
+
+int
+fs_init(void)
+{
+	xdg_init();
+
+	join_path(config_path, config_path_base, "/config",
+	    sizeof(config_path));
+	join_path(lockfile_path, cache_path_base, "/lock",
+	    sizeof(lockfile_path));
+	join_path(bookmark_file, data_path_base, "/bookmarks.gmi",
+	    sizeof(bookmark_file));
+	join_path(known_hosts_file, data_path_base, "/known_hosts",
+	    sizeof(known_hosts_file));
+	join_path(known_hosts_tmp, cache_path_base,
+	    "/known_hosts.tmp.XXXXXXXXXX", sizeof(known_hosts_tmp));
+	join_path(session_file, cache_path_base, "/session",
+	    sizeof(session_file));
+	join_path(crashed_file, cache_path_base, "/crashed",
+	    sizeof(crashed_file));
+
 	return 1;
 }
 
blob - /dev/null
blob + d7b6931d477c232dd73707897491781aa4150416 (mode 644)
--- /dev/null
+++ fs.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef FS_H
+#define FS_H
+
+#include <limits.h>
+
+extern char	config_path[PATH_MAX];
+
+#endif
blob - 7af4b34ab6e3ceb2b2293fad3d137a3d6463f0c0
blob + b29b85415704dfea07ae512b66885e20f79ea08e
--- pages/about_help.gmi
+++ pages/about_help.gmi
@@ -56,7 +56,7 @@ Telescope strives to be familiar for vi/vim users too!
 
 Telescope is fully customizable.  The configuration file is
 
-> ~/.telescope/config
+> ~/.config/telescope/config
 
 By default Telescope doesn’t use colours too much in order to adapt to both light and dark-themed terminals.  This doesn’t mean that Telescope cannot use colours though!  See for example contrib/light.config and contrib/dark.config.
 
@@ -67,11 +67,11 @@ By default Telescope doesn’t use colours too much in
 
 All the ‘about:*’ pages can be locally overridden.  For example, to customise about:new create:
 
-> ~/.telescope/pages/about_new.gmi
+> ~/.config/telescope/pages/about_new.gmi
 
 about:bookmarks is the only page that doesn’t follow this pattern; it’s located at
 
-> ~/.telescope/bookmarks.gmi
+> ~/.local/share/telescope/bookmarks.gmi
 
 
 ## Protocol Proxies
blob - b7d11fff56505b844c2b07147aa9b9b5982456eb
blob + 9dd8771c3768e5133f73b60628a815f3eb6d4f01
--- pages/bookmarks.gmi
+++ pages/bookmarks.gmi
@@ -2,4 +2,4 @@
 
 No bookmarks yet!
 
-Create ~/.telescope/bookmarks.gmi or use ‘bookmark-page’.
+Create ~/.local/share/telescope/bookmarks.gmi or use ‘bookmark-page’.
blob - 4e30e954ab7655bb3ef4d8d6513b644df781192b
blob + 1546d7a1499832c05dec9700952b543cff9fd5d9
--- telescope.1
+++ telescope.1
@@ -39,7 +39,7 @@ Show all available colors and exit.
 .It Fl c Pa config
 Specify an alternative configuration file.
 By default
-.Pa $HOME/.telescope/config
+.Pa $HOME/.config/telescope/config
 is loaded.
 .It Fl h , Fl -help
 Display version and usage.
@@ -635,7 +635,7 @@ The following aliases are available during
 During the startup,
 .Nm
 reads the configuration file at
-.Pa ~/.telescope/config
+.Pa ~/.config/telescope/config
 or the one given with the
 .Fl c
 flag.
@@ -880,21 +880,21 @@ The user's terminal name.
 .El
 .Sh FILES
 .Bl -tag -width Ds -compact
-.It Pa ~/.telescope/bookmarks.gmi
-Bookmarks file.
-.It Pa ~/.telescope/config
+.It Pa ~/.config/telescope/config
 Default configuration file.
-.It Pa ~/.telescope/known_hosts
+.It Pa ~/.config/telescope/pages/about_*.gmi
+Overrides for built-in about: pages.
+.It Pa ~/.local/share/telescope/bookmarks.gmi
+Bookmarks file.
+.It Pa ~/.local/share/telescope/known_hosts
 Hash of the certificates for all the known hosts.
 Each line contains three fields: hostname with optional port number,
 hash of the certificate and a numeric flag.
-.It Pa ~/.telescope/lock
+.It Pa ~/.cache/telescope/lock
 Lock file used to prevent multiple instance of
 .Nm
 from running at the same time.
-.It Pa ~/.telescope/pages/about_*.gmi
-Overrides for built-in about: pages.
-.It Pa ~/.telescope/session
+.It Pa ~/.cache/telescope/session
 The list of tabs from the last session.
 Every line identifies a tab and contains three space-separated fields:
 the full URL, a comma-separated list of attributes and the cached
@@ -908,7 +908,7 @@ It's possible to browse
 .Dq the small web
 .Pq i.e. simple websites
 by using programs like the duckling-proxy by defining a proxy in
-.Pa ~/.telescope/config :
+.Pa ~/.config/telescope/config :
 .Bd -literal -offset indent
 proxy http via "gemini://127.0.0.1:1965"
 proxy https via "gemini://127.0.0.1:1965"
blob - bee474089af045b48224937ec8bd34bc6ca4793e
blob + 31a077546609381a7b422b13497c50095ac2e693
--- telescope.c
+++ telescope.c
@@ -28,6 +28,7 @@
 #include <unistd.h>
 
 #include "defaults.h"
+#include "fs.h"
 #include "minibuffer.h"
 #include "parser.h"
 #include "session.h"
@@ -1066,7 +1067,7 @@ main(int argc, char * const *argv)
 	int		 proc = -1;
 	int		 sessionfd = -1;
 	int		 status;
-	char		 path[PATH_MAX], url[GEMINI_URL_LEN+1];
+	char		 url[GEMINI_URL_LEN+1];
 	const char	*argv0;
 
 	argv0 = argv[0];
@@ -1077,8 +1078,7 @@ main(int argc, char * const *argv)
 	if (getenv("NO_COLOR") != NULL)
 		enable_colors = 0;
 
-	strlcpy(path, getenv("HOME"), sizeof(path));
-	strlcat(path, "/.telescope/config", sizeof(path));
+	fs_init();
 
 	while ((ch = getopt_long(argc, argv, opts, longopts, NULL)) != -1) {
 		switch (ch) {
@@ -1086,7 +1086,7 @@ main(int argc, char * const *argv)
 			exit(ui_print_colors());
 		case 'c':
 			fail = 1;
-			strlcpy(path, optarg, sizeof(path));
+			strlcpy(config_path, optarg, sizeof(config_path));
 			break;
 		case 'n':
 			configtest = 1;
@@ -1143,7 +1143,7 @@ main(int argc, char * const *argv)
 	TAILQ_INIT(&minibuffer_map.m);
 
 	config_init();
-	parseconfig(path, fail);
+	parseconfig(config_path, fail);
 	if (configtest) {
 		puts("config OK");
 		exit(0);
@@ -1153,7 +1153,6 @@ main(int argc, char * const *argv)
 	    (download_path = strdup("/tmp/")) == NULL)
 		errx(1, "strdup");
 
-	fs_init();
 	if (!safe_mode && (sessionfd = lock_session()) == -1)
 		errx(1, "can't lock session, is another instance of "
 		    "telescope already running?");