summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--README.md6
-rw-r--r--build.zig45
-rw-r--r--build.zig.zon22
-rwxr-xr-xdmenu_run6
-rw-r--r--src/main.zig202
6 files changed, 283 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..880cd5d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+zig-out
+.zig-cache
diff --git a/README.md b/README.md
index f4c9db2..90fb9df 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,8 @@
# dmenu-runner
runner for dmenu that parses xdg entries
+
+usage:
+
+build with zig
+
+run using dmenu_run script
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..cc723ee
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,45 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const exe_mod = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ });
+
+ const folders = b.dependency( "known_folders", .{} ).module( "known-folders" );
+ const exe = b.addExecutable(.{
+ .name = "dmenu_runner",
+ .root_module = exe_mod,
+ });
+
+ exe.root_module.addImport( "known_folders", folders );
+
+ const check_exe = b.addExecutable(.{
+ .name = "dmenu_runner",
+ .root_source_file = b.path( "src/main.zig" ),
+ .target = target,
+ .optimize = optimize,
+ });
+
+ for( exe.root_module.import_table.keys() ) |key|
+ check_exe.root_module.addImport( key, exe.root_module.import_table.get( key ) orelse unreachable );
+
+ const check = b.step( "check", "check compile result" );
+ check.dependOn( &check_exe.step );
+
+ b.installArtifact(exe);
+
+ const run_cmd = b.addRunArtifact(exe);
+ run_cmd.step.dependOn(b.getInstallStep());
+
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+}
diff --git a/build.zig.zon b/build.zig.zon
new file mode 100644
index 0000000..d3cb95b
--- /dev/null
+++ b/build.zig.zon
@@ -0,0 +1,22 @@
+.{
+ .name = .dmenu_runner,
+ .version = "0.0.0",
+
+ .fingerprint = 0x116af3126414f43b, // Changing this has security and trust implications.
+ .minimum_zig_version = "0.14.0",
+
+ .dependencies = .{
+ .known_folders = .{
+ .url = "git+https://github.com/ziglibs/known-folders#aa24df42183ad415d10bc0a33e6238c437fc0f59",
+ .hash = "known_folders-0.0.0-Fy-PJtLDAADGDOwYwMkVydMSTp_aN-nfjCZw6qPQ2ECL",
+ },
+ },
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ // For example...
+ //"LICENSE",
+ //"README.md",
+ },
+}
diff --git a/dmenu_run b/dmenu_run
new file mode 100755
index 0000000..e67a02b
--- /dev/null
+++ b/dmenu_run
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+opt=$(dmenu_runner | dmenu -i -l 20)
+res=$(echo $opt | awk -F " " '{print $NF}')
+
+exo-open $res
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..7cd5caf
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,202 @@
+const z = @import( "std" );
+const folders = @import( "known_folders" );
+var gpa = z.heap.GeneralPurposeAllocator( .{ .thread_safe = true } ){};
+const alloc = gpa.allocator();
+
+const bufprint = z.fmt.bufPrint;
+const allocprint = z.fmt.allocPrint;
+const ArrayList = z.ArrayList;
+
+var paths = [_][]const u8{
+ "/usr/share/applications",
+ "/usr/local/share/applications",
+ "~/.local/share/applications",
+};
+
+const FsEntry = struct {
+ fn deinit( s: *const FsEntry ) void {
+ alloc.free( s.path );
+ alloc.free( s.name );
+ }
+
+ path: []const u8,
+ name: []const u8,
+ is_dir: bool
+};
+
+const XdgEntry = struct {
+ fn deinit( s: *const XdgEntry ) void {
+ alloc.free( s.path );
+ alloc.free( s.name );
+ alloc.free( s.desc );
+ alloc.free( s.exec );
+ }
+
+ path: []const u8,
+ name: []const u8,
+ desc: []const u8,
+ exec: []const u8,
+};
+
+pub fn checkSymlink( path: []const u8 ) !void {
+ const link_path_buf = try alloc.alloc( u8, z.fs.max_path_bytes );
+ var link_path = try z.fs.readLinkAbsolute( path, link_path_buf[0..z.fs.max_path_bytes] );
+
+ const is_abs = z.fs.path.isAbsolute( link_path );
+ if( !is_abs )
+ link_path = try z.fs.path.resolve( alloc, &[_][]const u8{ path, "..", link_path } );
+
+ defer if( !is_abs ) alloc.free( link_path );
+
+ const f = try z.fs.openFileAbsolute( link_path, .{} );
+ defer f.close();
+
+ const meta = try f.metadata();
+ if( meta.kind() != .file )
+ return error.NotAFile;
+}
+
+pub fn openDir( path: []const u8 ) !ArrayList( FsEntry ) {
+ var handle = try z.fs.openDirAbsolute( path, .{ .iterate = true, .no_follow = false } );
+ defer handle.close();
+
+ var list = ArrayList( FsEntry ).init( alloc );
+
+ var iter = handle.iterate();
+ while( try iter.next() ) |entry| {
+ if( !z.mem.endsWith( u8, entry.name, ".desktop" ) )
+ continue;
+
+ const path_buf = try alloc.alloc( u8, entry.name.len + path.len + 1 );
+ const name = try alloc.dupe( u8, entry.name );
+ const entry_path = try bufprint( path_buf, "{s}/{s}", .{path, entry.name} );
+
+ if( entry.kind == .sym_link ) {
+ checkSymlink( entry_path ) catch continue;
+ }
+
+ const fs_entry = FsEntry{
+ .name = name,
+ .path = entry_path,
+ .is_dir = entry.kind == .directory
+ };
+
+ list.append( fs_entry ) catch {};
+ }
+
+ return list;
+}
+
+pub fn parseXDGEntry( contents: []const u8, path: []const u8 ) !XdgEntry {
+ var i: u32 = 0;
+ var s: u32 = 0;
+
+ var name: ?[]const u8 = null;
+ var exec: ?[]const u8 = null;
+ var desc: ?[]const u8 = null;
+
+ for( contents ) |c| {
+ if( c == '\n' ) {
+ const line = contents[s..i];
+ if( name == null ) {
+ if( z.mem.startsWith( u8, line, "Name=" ) )
+ name = try alloc.dupe( u8, line[5..line.len] );
+ }
+ if( exec == null ) {
+ if( z.mem.startsWith( u8, line, "Exec=" ) )
+ exec = try alloc.dupe( u8, line[5..line.len] );
+ }
+ if( desc == null ) {
+ if( z.mem.startsWith( u8, line, "Comment=" ) )
+ desc = try alloc.dupe( u8, line[8..i-s] );
+ }
+
+ s = i + 1;
+ }
+
+ i += 1;
+ }
+
+ errdefer {
+ if( name != null ) alloc.free( name.? );
+ if( exec != null ) alloc.free( exec.? );
+ if( desc != null ) alloc.free( desc.? );
+ }
+
+ if( name == null or exec == null or name.?.len == 0 or exec.?.len == 0 )
+ return error.InvalidEntry;
+ if( desc == null )
+ desc = "";
+
+ return XdgEntry {
+ .name = name.?,
+ .exec = exec.?,
+ .desc = desc.?,
+ .path = try alloc.dupe( u8, path )
+ };
+}
+
+pub fn parseXDGEntries( entries: ArrayList( FsEntry ) ) !ArrayList( XdgEntry ) {
+ var list = ArrayList( XdgEntry ).init( alloc );
+ for( entries.items ) |entry| {
+ if( entry.is_dir )
+ continue;
+
+ var handle = try z.fs.openFileAbsolute( entry.path, .{} );
+ defer handle.close();
+
+ var reader = handle.reader();
+ const buf = try reader.readAllAlloc(alloc, 999999);
+ defer alloc.free( buf );
+
+ const xdg = parseXDGEntry( buf, entry.path ) catch continue;
+
+ try list.append( xdg );
+ }
+
+ return list;
+}
+
+pub fn main() !void {
+ var all_entries = ArrayList( XdgEntry ).init( alloc );
+ for( paths ) |path| {
+ var line = path;
+ var free_path = false;
+
+ const idx = z.mem.indexOf( u8, path, "~" );
+ if( idx ) |i| {
+ const home = try folders.getPath( alloc, .home );
+ if( home == null )
+ return error.NoHome;
+
+ line = try allocprint( alloc, "{s}{s}", .{ home.?, line[i+1..] } );
+ alloc.free( home.? );
+ free_path = true;
+ }
+ defer { if( free_path ) alloc.free( line ); }
+
+ const entries = try openDir( line );
+ defer {
+ for( entries.items ) |e|
+ e.deinit();
+ entries.deinit();
+ }
+
+ const list = try parseXDGEntries( entries );
+ try all_entries.appendSlice( list.items );
+ list.deinit();
+ }
+
+ for( all_entries.items ) |e| {
+ if( e.desc.len > 0 ) {
+ try z.io.getStdOut().writer().print( "{s} - {s} | {s}\n", .{ e.name, e.desc, e.path } );
+ } else
+ try z.io.getStdOut().writer().print( "{s} | {s}\n", .{ e.name, e.path } );
+
+ e.deinit();
+ }
+ all_entries.deinit();
+
+ _ = gpa.deinit();
+}
+