M CMakeLists.txt => CMakeLists.txt +23 -8
@@ 18,32 18,47 @@ include(KDECompilerSettings NO_POLICY_SCOPE)
find_package(PkgConfig REQUIRED)
pkg_check_modules(glib REQUIRED IMPORTED_TARGET glib-2.0)
+pkg_check_modules(magic REQUIRED IMPORTED_TARGET libmagic)
+
+find_package(CURL REQUIRED)
find_package(KF5 COMPONENTS Config)
set_package_properties(KF5::Config PROPERTIES TYPE OPTIONAL)
add_feature_info("KF5::Config" KF5CONFIG_FOUND "kconfig needed for reading KDE default terminal settings")
-set(rundesktop_SRC
+set(desktoputils_SRC
src/desktop.c
- src/rundesktop.c
+ src/desktoputils.c
src/execparser.c
src/argbuilder.c
- src/config.c)
+ src/config.c
+ src/mime.c
+ src/url.c)
+
+set(rundesktop_SRC
+ src/rundesktop.c)
+
+set(duopen_SRC
+ src/duopen.c)
if(KF5CONFIG_FOUND)
add_compile_definitions(WITH_KDE)
- set(rundesktop_SRC
- ${rundesktop_SRC}
+ set(desktoputils_SRC
+ ${desktoputils_SRC}
src/kdeterminal.cpp)
endif()
+add_library(desktoputils STATIC ${desktoputils_SRC})
add_executable(rundesktop ${rundesktop_SRC})
+add_executable(duopen ${duopen_SRC})
-target_link_libraries(rundesktop PkgConfig::glib)
+target_link_libraries(desktoputils PRIVATE PkgConfig::glib PkgConfig::magic)
+target_link_libraries(rundesktop desktoputils)
+target_link_libraries(duopen desktoputils curl)
if(KF5CONFIG_FOUND)
- target_link_libraries(rundesktop KF5::ConfigCore)
+ target_link_libraries(desktoputils PRIVATE KF5::ConfigCore)
endif()
-target_include_directories(rundesktop PRIVATE ${CMAKE_BINARY_DIR})
+target_include_directories(desktoputils PUBLIC ${CMAKE_BINARY_DIR})
A src/desktoputils.c => src/desktoputils.c +6 -0
@@ 0,0 1,6 @@
+#include "desktoputils.h"
+
+bool du_strleq(const char *a, const char *b, size_t b_len)
+{
+ return strlen(a) == b_len && strncmp(a, b, b_len) == 0;
+}
M src/desktoputils.h => src/desktoputils.h +5 -0
@@ 2,6 2,9 @@
#define DESKTOPUTILS_H
#include <error.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
#include <sysexits.h>
#define EXPORT __attribute__((__visibility__("default")))
@@ 10,6 13,8 @@
#define UNREACHABLE() error_at_line(EX_SOFTWARE, 0, __FILE__, __LINE__, "unreachable")
+bool du_strleq(const char *a, const char *b, size_t b_len);
+
#ifdef __clang__
#define UNSAFE_CAST_BEGIN _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"")
A src/duopen.c => src/duopen.c +67 -0
@@ 0,0 1,67 @@
+#include "desktoputils.h"
+#include "mime.h"
+#include "url.h"
+#include "ver.h"
+#include <argp.h>
+#include <assert.h>
+#include <string.h>
+#include <sysexits.h>
+
+EXPORT const char *argp_program_version = "duopen " desktoputils_VERSION_STRING;
+EXPORT const char *argp_program_bug_address = "Marco Rebhan <me@dblsaiko.net>";
+
+char doc[] = "Opens a URL in a program associated with its contents";
+
+struct argp_option options[] = {
+ {0},
+};
+
+error_t parse_opt(int key, char *arg, struct argp_state *state);
+
+struct argp argp = {options, parse_opt, "URL", doc, 0, 0, 0};
+
+char *url;
+
+int main(int argc, char **argv)
+{
+ argp_parse(&argp, argc, argv, 0, 0, 0);
+
+ url_info_t u = url_parse(url);
+
+ if (!u.is_url || du_strleq("file", u.scheme, u.scheme_len)) {
+ const char *path = u.is_url ? u.path : url;
+ const char *mime_type = mime_type_for_file(path);
+ assert(mime_type);
+ puts(mime_type);
+ } else if (du_strleq("http", u.scheme, u.scheme_len) || du_strleq("https", u.scheme, u.scheme_len)) {
+ const char *mime_type = mime_type_for_url(u.path);
+ assert(mime_type);
+ puts(mime_type);
+ } else {
+ error(EX_DATAERR, 0, "unhandled URL scheme: %.*s", (int)u.scheme_len, u.scheme);
+ }
+}
+
+error_t parse_opt(int key, char *arg, struct argp_state *state)
+{
+ switch (key) {
+ case ARGP_KEY_ARG:
+ if (!url) {
+ url = arg;
+ } else {
+ argp_failure(state, EX_USAGE, 0, "extra arguments");
+ }
+
+ break;
+ case ARGP_KEY_END:
+ if (!url) {
+ argp_failure(state, EX_USAGE, 0, "missing URL");
+ }
+
+ break;
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+
+ return 0;
+}
M src/kdeterminal.cpp => src/kdeterminal.cpp +1 -2
@@ 8,8 8,7 @@ extern "C" const char *kde_selected_terminal()
static char *preferred_terminal = nullptr;
if (preferred_terminal) {
- delete[] preferred_terminal;
- preferred_terminal = nullptr;
+ return preferred_terminal;
}
KConfigGroup conf_group(KSharedConfig::openConfig(), QStringLiteral("General"));
M src/kdeterminal.h => src/kdeterminal.h +1 -1
@@ 5,7 5,7 @@
extern "C" {
#endif
-const char *kde_selected_terminal();
+const char *kde_selected_terminal(void);
#ifdef __cplusplus
}
A src/mime.c => src/mime.c +75 -0
@@ 0,0 1,75 @@
+#include "mime.h"
+#include <assert.h>
+#include <curl/curl.h>
+#include <error.h>
+#include <magic.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+const char *mime_type_for_file(const char *path)
+{
+ static magic_t magic = NULL;
+
+ if (!magic) {
+ magic = magic_open(MAGIC_MIME_TYPE);
+ magic_load(magic, NULL);
+ }
+
+ return magic_file(magic, path);
+}
+
+const char *mime_type_for_url(const char *url)
+{
+ static bool i_init = false;
+
+ if (!i_init) {
+ CURLcode res = curl_global_init(0);
+
+ if (res != CURLE_OK) {
+ error(EX_SOFTWARE, 0, "cURL error during init: %d", res);
+ }
+
+ i_init = true;
+ }
+
+ CURL *curl = curl_easy_init();
+ assert(curl); // hopefully, this should always give us a thingy
+
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 1); // HEAD request
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
+
+ CURLcode res = curl_easy_perform(curl);
+
+ if (res != CURLE_OK) {
+ error(EX_SOFTWARE, 0, "cURL error: %d", res);
+ }
+
+ long response_code;
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
+
+ static char *content_type_out = NULL;
+
+ if (response_code != 200) {
+ curl_easy_cleanup(curl);
+ return NULL;
+ }
+
+ char *content_type = NULL;
+ curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type);
+
+ if (content_type) {
+ size_t size = strlen(content_type) + 1;
+ content_type_out = realloc(content_type_out, size);
+ memcpy(content_type_out, content_type, size);
+
+ curl_easy_cleanup(curl);
+ return content_type_out;
+ } else {
+ curl_easy_cleanup(curl);
+ return "application/octet-stream";
+ }
+}
A src/mime.h => src/mime.h +8 -0
@@ 0,0 1,8 @@
+#ifndef MIME_H
+#define MIME_H
+
+const char *mime_type_for_file(const char *path);
+
+const char *mime_type_for_url(const char *url);
+
+#endif
M src/rundesktop.c => src/rundesktop.c +2 -2
@@ 237,13 237,13 @@ error_t parse_opt(int key, char *arg, struct argp_state *state)
files_len += 1;
} else {
// we should never hit this but just in case something weird happens
- argp_failure(state, 1, 0, "too many arguments");
+ argp_failure(state, EX_USAGE, 0, "too many arguments");
}
break;
case ARGP_KEY_END:
if (!desktop_file) {
- argp_failure(state, 1, 0, "missing desktop file");
+ argp_failure(state, EX_USAGE, 0, "missing desktop file");
}
break;
A src/url.c => src/url.c +70 -0
@@ 0,0 1,70 @@
+#include "url.h"
+#include <ctype.h>
+#include <string.h>
+
+url_info_t url_parse(const char *str)
+{
+ url_info_t result = {0};
+
+ // search for end of scheme
+ size_t pos = 0;
+
+ while (true) {
+ if (!str[pos]) {
+ // reached the end of the string without ':' found -> not a URL
+ return result;
+ } else if (str[pos] == ':') {
+ if (pos == 0) {
+ // scheme of length 0 is invalid
+ return result;
+ } else {
+ break;
+ }
+ } else if (!isalnum(str[pos])) {
+ // accept only alphanumeric chars in the scheme
+ return result;
+ }
+
+ pos += 1;
+ }
+
+ // now we have a valid scheme in str[..pos], we don't really care about the
+ // actual path since we just pass it off to either curl or the fs so nothing
+ // needs to be parsed there
+
+ result.scheme = str;
+ result.scheme_len = pos;
+ pos += 1;
+
+ if (du_strleq("file", result.scheme, result.scheme_len)) {
+ // file URLs are special since they don't need to start with file://,
+ // instead file:/xyz.txt refers to /xyz.txt, but file://host/xyz.txt
+ // refers to xyz.txt on host (which can also be empty for a local path)
+
+ if (strncmp("//", str + pos, 2) == 0) {
+ if (str[pos + 2] == '/') {
+ // we have 'file:///' which is an absolute path (so keep the
+ // third '/'!)
+ pos += 2;
+ } else {
+ // this is a remote url. poo! keep it as '//host/path' for now,
+ // like an SMB path
+ }
+ } else {
+ // we have 'file:relpath' or 'file:/abspath', don't strip anything
+ }
+ } else {
+ // other URLs always have to have the '//'
+
+ if (strncmp("//", str + pos, 2) != 0) {
+ return result;
+ }
+
+ pos += 2;
+ }
+
+ result.path = str + pos;
+
+ result.is_url = true;
+ return result;
+}
A src/url.h => src/url.h +17 -0
@@ 0,0 1,17 @@
+#ifndef URL_H
+#define URL_H
+
+#include "desktoputils.h"
+#include <stdbool.h>
+#include <stddef.h>
+
+typedef struct url_info {
+ bool is_url;
+ const char *scheme;
+ size_t scheme_len;
+ const char *path;
+} url_info_t;
+
+EXPORT url_info_t url_parse(const char *str);
+
+#endif