commit 2a435930e5f8dde8892afa86bdb7765cc82e3119
Author: Dominik Schmidt <dominik@schm1dt.ch>
Date: Wed, 26 Feb 2020 22:24:59 +0100
Initial commit
Diffstat:
A | Makefile | | | 39 | +++++++++++++++++++++++++++++++++++++++ |
A | src/open.d | | | 69 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/xdg/desktop.d | | | 172 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/xdg/mime.d | | | 140 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
4 files changed, 420 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,39 @@
+DMD ?= dmd
+DEBUG ?= -g
+RELEASE ?= -release
+DFLAGS ?= -O
+INCLUDES = src
+SRC := $(wildcard src/xdg/*.d )
+TARGET = xdg
+LIBS = magic
+
+_DFLAGS := $(addprefix -I, $(INCLUDES)) $(addprefix -L-l, $(LIBS)) $(DFLAGS)
+
+.PHONY: all shared static clean distclean
+
+all: shared static xdg-open
+
+xdg-open: src/open.o lib$(TARGET).a
+ $(DMD) $(_DFLAGS) $(RELEASE) -of$@ $^
+
+shared: lib$(TARGET)-debug.so lib$(TARGET).so
+static: lib$(TARGET)-debug.a lib$(TARGET).a
+
+lib$(TARGET).a: $(patsubst %.d,%.o,$(SRC))
+ $(DMD) $(_DFLAGS) $(RELEASE) -lib -of$@ $^
+lib$(TARGET)-debug.a: $(patsubst %.d, %-debug.o,$(SRC))
+ $(DMD) $(_DFLAGS) $(DEBUG) -lib -of$@ $^
+lib$(TARGET).so: $(patsubst %.d, %.o, $(SRC))
+ $(DMD) $(_DFLAGS) $(RELEASE) -shared -of$@ $^
+lib$(TARGET)-debug.so: $(patsubst %.d, %-debug.o, $(SRC))
+ $(DMD) $(_DFLAGS) $(DEBUG) -shared -of$@ $^
+
+%.o:%.d
+ $(DMD) $(_DFLAGS) $(RELEASE) -c -of$@ $<
+%-debug.o:%.d
+ $(DMD) $(_DFLAGS) $(DEBUG) -c -of$@ $<
+
+clean:
+ rm -f $(patsubst %.d, %.o, $(SRC)) $(patsubst %.d,%-debug.o,$(SRC))
+distclean: clean
+ rm -f lib$(TARGET){,-debug}.{so,a}
diff --git a/src/open.d b/src/open.d
@@ -0,0 +1,69 @@
+import xdg.desktop;
+import xdg.mime;
+import std.stdio;
+import std.process;
+import std.array;
+import std.regex;
+import std.algorithm.searching:startsWith;
+import std.algorithm:splitter;
+import std.format;
+
+bool is_file_url_or_path(in char[] uri){
+ if(uri.startsWith("file://")){
+ return true;
+ }
+ auto r = ctRegex!(`^\w[\w+-\.]*:`);
+ if(uri.matchFirst(r)){
+ return false;
+ }
+ return true;
+}
+
+struct Opener{
+ Mime mime;
+ Database db;
+
+ void init(){
+ mime.init();
+ db.init();
+ }
+
+
+ void open(in char[] file, in char[] mime){
+ auto ent = db.get(mime);
+ spawnShell(ent.format(file));
+ }
+
+ void open(in char[] uri){
+ if(is_file_url_or_path(uri)){
+ auto file = uri.file_url_to_path();
+ auto type = mime.file(file);
+ open(file, type);
+ }
+ else{
+ auto file = uri;
+ auto browsers = environment.get("BROWSER","x-www-browser:firefox:iceweasel:seamonkey:mozilla:epiphany:konqueror:chromium:chromium-browser:google-chrome:www-browser:links2:elinks:links:lynx:w3m");
+ foreach(browser; browsers.splitter(":")){
+ auto pid = spawnShell(format!(`%s "%s"`)(browser,uri));
+ if(wait(pid) == 0){
+ break;
+ }
+ }
+ }
+ }
+
+}
+
+void main(string[] args){
+ Opener o;
+ o.init();
+ //foreach(arg;args[1..$]){
+ auto arg = args[1..$].join(" ");
+ try{
+ o.open(arg);
+ }
+ catch(Exception e){
+ stderr.writeln("Could not open ", arg, " ", e.msg);
+ }
+ //}
+}
diff --git a/src/xdg/desktop.d b/src/xdg/desktop.d
@@ -0,0 +1,172 @@
+module xdg.desktop;
+import std.stdio;
+import std.string;
+import std.path;
+import std.process;
+import std.algorithm:joiner,splitter,startsWith,skipOver,findSplit,map;
+import std.range;
+import std.file;
+import std.format;
+import std.array;
+import xdg.mime;
+
+
+auto file_url_to_path(inout(char)[] uri){
+ uri.skipOver("file://localhost");
+ uri.skipOver("file://");
+ return uri;
+}
+
+auto xdg_user_dir(){
+ auto home = environment.get("XDG_DATA_HOME", buildPath(environment.get("HOME"), ".local/share"));
+ auto sys = environment.get("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/");
+ return chain(home.splitter(":"),sys.splitter(":")).map!(a=>buildPath(a,"applications"));
+}
+
+struct Database{
+ struct Entry{
+ string path;
+ string baseName;
+ string name;
+ string icon;
+ string exec;
+ string[] mimeTypes;
+
+ auto format(in char[] path){
+ Appender!string app;
+ auto fmt = exec;
+ while(fmt.length>0){
+ auto s = fmt.findSplit("%");
+ app ~= s[0];
+ if(s[1].length==0){
+ break;
+ }
+ switch(s[2][0]){
+ case '%':
+ app ~= "%";
+ break;
+ case 'i':
+ app ~= "--icon "~icon;
+ break;
+ case 'f':
+ case 'F':
+ case 'u':
+ case 'U':
+ app ~= '"'~path.replace("\"","\\\"")~'"';
+ break;
+ default:
+ throw new Exception("Unknown format literal {}".format(s[2]));
+ break;
+ }
+ fmt = s[2][1..$];
+ }
+ return app.data;
+ }
+
+ void fromFile(in char[] file){
+ auto f = File(file, "r");
+ scope(exit){
+ f.close();
+ }
+ path = file.idup;
+ baseName = .baseName(path);
+ foreach(l; f.byLine){
+ auto s = l.findSplit("=");
+ switch(s[0]){
+ case "Name":
+ name = s[2].idup;
+ break;
+ case "Exec":
+ exec = s[2].idup;
+ break;
+ case "Icon":
+ icon = s[2].idup;
+ break;
+ case "MimeType":
+ mimeTypes = s[2].idup.split(";");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ Entry[][string] entries;
+ Entry[][string] entries_by_basename;
+ Entry[string] preferred;
+ void index(in char[] path){
+ writeln("Indexing ", path);
+ auto de = dirEntries(cast(string)path, "*.desktop", SpanMode.breadth);
+ foreach(entry; de){
+ Entry e;
+ try{
+ e.fromFile(entry);
+ }
+ catch(Exception exc){
+
+ writeln(exc);
+ }
+ foreach(mt; e.mimeTypes){
+ entries[mt] ~= e;
+ }
+ entries_by_basename[e.baseName] ~= e;
+ }
+ }
+
+ void index(){
+ auto paths = xdg_user_dir();
+ foreach(path; paths){
+ if(!exists(path))
+ continue;
+ try{
+ index(path);
+ }
+ catch(Exception e){
+ writeln(e);
+ }
+ }
+
+ }
+
+ void loadPreferred(){
+ auto config_home = environment.get("XDG_CONFIG_HOME",buildPath(environment.get("HOME"), ".config"));
+ auto config = buildPath(config_home, "mimeapps.list");
+ if(exists(config)){
+ auto f = File(config);
+ scope(exit){
+ f.close();
+ }
+ foreach(l; f.byLine()){
+ auto s = l.findSplit("=");
+ if(s[1].length > 0){
+ auto de = s[2].findSplit(";")[0];
+ if(auto entries = de in entries_by_basename){
+ preferred[s[0].idup] = (*entries)[0];
+ }
+ else{
+ stderr.writeln("No entry for %s found".format(de));
+ }
+ }
+ }
+ }
+ }
+
+ void init(){
+ index();
+ loadPreferred();
+ }
+
+ Entry get(in char[] mime){
+ if(auto e = mime in preferred){
+ return *e;
+ }
+ else if(auto e = mime in entries){
+ return (*e)[0];
+ }
+ else{
+ throw new Exception("Mime type %s not found".format(mime));
+ }
+ }
+}
+
diff --git a/src/xdg/mime.d b/src/xdg/mime.d
@@ -0,0 +1,140 @@
+module xdg.mime;
+import std.string;
+
+extern(C){
+ enum MAGIC:int{
+ NONE =0x0000000,
+ DEBUG =0x0000001,
+ SYMLINK =0x0000002,
+ COMPRESS =0x0000004,
+ DEVICES =0x0000008,
+ MIME_TYPE =0x0000010,
+ CONTINUE =0x0000020,
+ CHECK =0x0000040,
+ PRESERVE_ATIME =0x0000080,
+ RAW =0x0000100,
+ ERROR =0x0000200,
+ MIME_ENCODING =0x0000400,
+ APPLE =0x0000800,
+ EXTENSION =0x1000000,
+ COMPRESS_TRANSP =0x2000000,
+ NO_CHECK_COMPRESS =0x0001000,
+ NO_CHECK_TAR =0x0002000,
+ NO_CHECK_SOFT =0x0004000,
+ NO_CHECK_APPTYPE =0x0008000,
+ NO_CHECK_ELF =0x0010000,
+ NO_CHECK_TEXT =0x0020000,
+ NO_CHECK_CDF =0x0040000,
+ NO_CHECK_CSV =0x0080000,
+ NO_CHECK_TOKENS =0x0100000,
+ NO_CHECK_ENCODING =0x0200000,
+ NO_CHECK_JSON =0x0400000,
+ NODESC =(MAGIC.EXTENSION|MAGIC.MIME|MAGIC.APPLE),
+ MIME =(MAGIC.MIME_TYPE|MAGIC.MIME_ENCODING),
+ }
+
+
+ alias magic_t = void*;
+
+ magic_t magic_open(MAGIC);
+ void magic_close(magic_t);
+
+ const(char)* magic_getpath(const char *, int);
+ const(char)* magic_file(magic_t, const char *);
+ const(char)* magic_descriptor(magic_t, int);
+ const(char)* magic_buffer(magic_t, const void *, size_t);
+
+ const(char)* magic_error(magic_t);
+ int magic_getflags(magic_t);
+ int magic_setflags(magic_t, int);
+
+ int magic_version();
+ int magic_load(magic_t, const char *);
+ int magic_load_buffers(magic_t, void **, size_t *, size_t);
+
+ int magic_compile(magic_t, const char *);
+ int magic_check(magic_t, const char *);
+ int magic_list(magic_t, const char *);
+ int magic_errno(magic_t);
+
+}
+
+class MagicException : Exception {
+ this(Magic m, string file=__FILE__, size_t line=__LINE__, Throwable next=null){
+ auto errmsg = magic_error(m.cookie);
+ super(errmsg.fromStringz.idup,file,line,next);
+ }
+}
+
+struct Magic{
+ private magic_t cookie = null;
+ bool loaded=false;
+
+ bool ready(){
+ return cookie != null && loaded;
+ }
+
+ void open(MAGIC m){
+ cookie = magic_open(m);
+ if(cookie == null){
+ throw new MagicException(this);
+ }
+ }
+
+ void open(){
+ open(MAGIC.NONE);
+ }
+
+ private void check(int val){
+ if(val<0){
+ throw new MagicException(this);
+ }
+ }
+
+ void load(){
+ check(magic_load(cookie, null));
+ loaded = true;
+ }
+
+ void load(in char[] path){
+ check(magic_load(cookie, path.toStringz));
+ loaded = true;
+ }
+
+ auto file(in char[] path)
+ in{
+ assert(ready());
+ }
+ body{
+ auto res = magic_file(cookie, path.toStringz);
+ if(res == null){
+ throw new MagicException(this);
+ }
+ return res.fromStringz;
+ }
+
+ void close(){
+ if(cookie)
+ magic_close(cookie);
+ cookie = null;
+ loaded = false;
+ }
+
+ static int Version(){
+ return magic_version();
+ }
+}
+
+struct Mime{
+ Magic m;
+ void init(){
+ m.open(MAGIC.MIME_TYPE);
+ m.load();
+ }
+ auto file(in char[] path){
+ return m.file(path);
+ }
+ void close(){
+ m.close();
+ }
+}