about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--config.h2
-rw-r--r--key.c26
-rw-r--r--wm.c29
-rw-r--r--wm.h15
5 files changed, 44 insertions, 30 deletions
diff --git a/Makefile b/Makefile
index c5c8db4..32c9492 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@
 
 include config.mk
 
-WMSRC = bar.c client.c draw.c event.c util.c wm.c
+WMSRC = bar.c client.c cmd.c draw.c event.c key.c util.c wm.c
 WMOBJ = ${WMSRC:.c=.o}
 MENSRC = menu.c draw.c util.c
 MENOBJ = ${MENSRC:.c=.o}
diff --git a/config.h b/config.h
index 58ed8ea..d37034d 100644
--- a/config.h
+++ b/config.h
@@ -9,3 +9,5 @@
 #define BORDERCOLOR	"#000000"
 #define STATUSCMD	"echo -n `date` `uptime | sed 's/.*://; s/,//g'`" \
 					" `acpi | awk '{print $4}' | sed 's/,//'`"
+#define KEYS		\
+	{ Mod1Mask, XK_Return, run, "xterm -u8 -bg black -fg white -fn -*-terminus-medium-*-*-*-14-*-*-*-*-*-iso10646-*" },
diff --git a/key.c b/key.c
new file mode 100644
index 0000000..a8742da
--- /dev/null
+++ b/key.c
@@ -0,0 +1,26 @@
+/*
+ * (C)opyright MMVI Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+
+#include "wm.h"
+
+#include <X11/keysym.h>
+
+static Key key[] = {
+	KEYS
+};
+
+void
+update_keys()
+{
+	unsigned int i, len;
+	KeyCode code;
+
+	len = sizeof(key) / sizeof(key[0]);
+	for(i = 0; i < len; i++) {
+		code = XKeysymToKeycode(dpy, key[i].keysym);
+		XUngrabKey(dpy, code, key[i].mod, root);
+		XGrabKey(dpy, code, key[i].mod, root, True, GrabModeAsync, GrabModeAsync);
+	}
+}
diff --git a/wm.c b/wm.c
index a4caf64..038e6b9 100644
--- a/wm.c
+++ b/wm.c
@@ -24,7 +24,6 @@ Client *client = NULL;
 
 char *bartext, tag[256];
 int screen, sel_screen;
-unsigned int lock_mask, numlock_mask;
 
 /* draw structs */
 Brush brush = {0};
@@ -144,32 +143,6 @@ startup_error_handler(Display *dpy, XErrorEvent *error)
 }
 
 static void
-init_lock_keys()
-{
-	XModifierKeymap *modmap;
-	KeyCode numlock;
-	int i;
-	static int masks[] = {
-		ShiftMask, LockMask, ControlMask, Mod1Mask,
-		Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
-	};
-
-	numlock_mask = 0;
-	modmap = XGetModifierMapping(dpy);
-	numlock = XKeysymToKeycode(dpy, XStringToKeysym("Num_Lock"));
-
-	if(modmap && modmap->max_keypermod > 0) {
-		int max = (sizeof(masks) / sizeof(int)) * modmap->max_keypermod;
-		for(i = 0; i < max; i++)
-			if(numlock && (modmap->modifiermap[i] == numlock))
-				numlock_mask = masks[i / modmap->max_keypermod];
-	}
-	XFreeModifiermap(modmap);
-
-	lock_mask = 255 & ~(numlock_mask | LockMask);
-}
-
-static void
 cleanup()
 {
 	/*
@@ -243,7 +216,7 @@ main(int argc, char *argv[])
 	cursor[CurResize] = XCreateFontCursor(dpy, XC_sizing);
 	cursor[CurMove] = XCreateFontCursor(dpy, XC_fleur);
 
-	init_lock_keys();
+	update_keys();
 
 	brush.drawable = XCreatePixmap(dpy, root, rect.width, rect.height,
 			DefaultDepth(dpy, screen));
diff --git a/wm.h b/wm.h
index 01bbee4..454f7bf 100644
--- a/wm.h
+++ b/wm.h
@@ -20,6 +20,7 @@ enum { CurNormal, CurResize, CurMove, CurInput, CurLast };
 enum { RFloat, RGrid, RLast };
 
 typedef struct Client Client;
+typedef struct Key Key;
 
 struct Client {
 	char name[256];
@@ -36,6 +37,13 @@ struct Client {
 	Client *snext;
 };
 
+struct Key {
+	unsigned long mod;
+	KeySym keysym;
+	void (*func)(char *arg);
+	char *arg;
+};
+
 extern Display *dpy;
 extern Window root, barwin;
 extern Atom wm_atom[WMLast], net_atom[NetLast];
@@ -46,7 +54,6 @@ extern Bool grid;
 extern void (*handler[LASTEvent]) (XEvent *);
 
 extern int screen, sel_screen;
-extern unsigned int lock_mask, numlock_mask;
 extern char *bartext, tag[256];
 
 extern Brush brush;
@@ -55,9 +62,15 @@ extern Client *client;
 /* bar.c */
 extern void draw_bar();
 
+/* cmd.c */
+extern void run(char *arg);
+
 /* client.c */
 extern Client *create_client(Window w, XWindowAttributes *wa);
 extern void manage(Client *c);
 
+/* key.c */
+extern void update_keys();
+
 /* wm.c */
 extern int win_proto(Window w);
/* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
-- environment for immutable logs
-- optionally reads extensions for rendering some types from the source codebase that generated them
--
-- We won't care too much about long, wrapped lines. If they lines get too
-- long to manage, you need a better, graphical rendering for them. Load
-- functions to render them into the log_render namespace.

function source.initialize_log_browser_side()
  Log_browser_state = edit.initialize_state(Margin_top, Editor_state.right + Margin_right + Margin_left, (Editor_state.right+Margin_right)*2, Editor_state.font_height, Editor_state.line_height)
  Log_browser_state.filename = 'log'
  load_from_disk(Log_browser_state)  -- TODO: pay no attention to Fold
  log_browser.parse(Log_browser_state)
  Text.redraw_all(Log_browser_state)
  Log_browser_state.screen_top1 = {line=1, pos=1}
  Log_browser_state.cursor1 = {line=1, pos=nil}
end

Section_stack = {}
Section_border_color = {r=0.7, g=0.7, b=0.7}
Cursor_line_background_color = {r=0.7, g=0.7, b=0, a=0.1}

Section_border_padding_horizontal = 30  -- TODO: adjust this based on font height (because we draw text vertically along the borders
Section_border_padding_vertical = 15  -- TODO: adjust this based on font height

log_browser = {}

function log_browser.parse(State)
  for _,line in ipairs(State.lines) do
    if line.data ~= '' then
      line.filename, line.line_number, line.data = line.data:match('%[string "([^:]*)"%]:([^:]*):%s*(.*)')
      line.filename = guess_source(line.filename)
      line.line_number = tonumber(line.line_number)
      if line.data:sub(1,1) == '{' then
        local data = json.decode(line.data)
        if log_render[data.name] then
          line.data = data
        end
        line.section_stack = table.shallowcopy(Section_stack)
      elseif line.data:match('\u{250c}') then
        line.section_stack = table.shallowcopy(Section_stack)  -- as it is at the beginning
        local section_name = line.data:match('\u{250c}%s*(.*)')
        table.insert(Section_stack, {name=section_name})
        line.section_begin = true
        line.section_name = section_name
        line.data = nil
      elseif line.data:match('\u{2518}') then
        local section_name = line.data:match('\u{2518}%s*(.*)')
        if array.find(Section_stack, function(x) return x.name == section_name end) then
          while table.remove(Section_stack).name ~= section_name do
            --
          end
          line.section_end = true
          line.section_name = section_name
          line.data = nil
        end
        line.section_stack = table.shallowcopy(Section_stack)
      else
        -- string
        line.section_stack = table.shallowcopy(Section_stack)
      end
    else
      line.section_stack = {}
    end
  end
end

function table.shallowcopy(x)
  return {unpack(x)}
end

function guess_source(filename)
  local possible_source = filename:gsub('%.lua$', '%.splua')
  if file_exists(possible_source) then
    return possible_source
  else
    return filename
  end
end

function log_browser.draw(State)
  assert(#State.lines == #State.line_cache)
  local mouse_line_index = log_browser.line_index(State, App.mouse_x(), App.mouse_y())
  local y = State.top
  for line_index = State.screen_top1.line,#State.lines do
    App.color(Text_color)
    local line = State.lines[line_index]
    if y + State.line_height > App.screen.height then break end
    local height = State.line_height
    if should_show(line) then
      local xleft = render_stack_left_margin(State, line_index, line, y)
      local xright = render_stack_right_margin(State, line_index, line, y)
      if line.section_name then
        App.color(Section_border_color)
        local section_text = to_text(line.section_name)
        if line.section_begin then
          local sectiony = y+Section_border_padding_vertical
          love.graphics.line(xleft,sectiony, xleft,y+State.line_height)
          love.graphics.line(xright,sectiony, xright,y+State.line_height)
          love.graphics.line(xleft,sectiony, xleft+50-2,sectiony)
          love.graphics.draw(section_text, xleft+50,y)
          love.graphics.line(xleft+50+App.width(section_text)+2,sectiony, xright,sectiony)
        else assert(line.section_end)
          local sectiony = y+State.line_height-Section_border_padding_vertical
          love.graphics.line(xleft,y, xleft,sectiony)
          love.graphics.line(xright,y, xright,sectiony)
          love.graphics.line(xleft,sectiony, xleft+50-2,sectiony)
          love.graphics.draw(section_text, xleft+50,y)
          love.graphics.line(xleft+50+App.width(section_text)+2,sectiony, xright,sectiony)
        end
      else
        if type(line.data) == 'string' then
          local old_left, old_right = State.left,State.right
          State.left,State.right = xleft,xright
          y = Text.draw(State, line_index, y, --[[startpos]] 1)
          State.left,State.right = old_left,old_right
        else
          height = log_render[line.data.name](line.data, xleft, y, xright-xleft)
        end
      end
      if App.mouse_x() > Log_browser_state.left and line_index == mouse_line_index then
        App.color(Cursor_line_background_color)
        love.graphics.rectangle('fill', xleft,y, xright-xleft, height)
      end
      y = y + height
    end
  end
end

function render_stack_left_margin(State, line_index, line, y)
  if line.section_stack == nil then
    -- assertion message
    for k,v in pairs(line) do
      print(k)
    end
  end
  App.color(Section_border_color)
  for i=1,#line.section_stack do
    local x = State.left + (i-1)*Section_border_padding_horizontal
    love.graphics.line(x,y, x,y+log_browser.height(State, line_index))
    if y < 30 then
      love.graphics.print(line.section_stack[i].name, x+State.font_height+5, y+5, --[[vertically]] math.pi/2)
    end
    if y > App.screen.height-log_browser.height(State, line_index) then
      love.graphics.print(line.section_stack[i].name, x+State.font_height+5, App.screen.height-App.width(to_text(line.section_stack[i].name))-5, --[[vertically]] math.pi/2)
    end
  end
  return log_browser.left_margin(State, line)
end

function render_stack_right_margin(State, line_index, line, y)
  App.color(Section_border_color)
  for i=1,#line.section_stack do
    local x = State.right - (i-1)*Section_border_padding_horizontal
    love.graphics.line(x,y, x,y+log_browser.height(State, line_index))
    if y < 30 then
      love.graphics.print(line.section_stack[i].name, x, y+5, --[[vertically]] math.pi/2)
    end
    if y > App.screen.height-log_browser.height(State, line_index) then
      love.graphics.print(line.section_stack[i].name, x, App.screen.height-App.width(to_text(line.section_stack[i].name))-5, --[[vertically]] math.pi/2)
    end
  end
  return log_browser.right_margin(State, line)
end

function should_show(line)
  -- Show a line if every single section it's in is expanded.
  for i=1,#line.section_stack do
    local section = line.section_stack[i]
    if not section.expanded then
      return false
    end
  end
  return true
end

function log_browser.left_margin(State, line)
  return State.left + #line.section_stack*Section_border_padding_horizontal
end

function log_browser.right_margin(State, line)
  return State.right - #line.section_stack*Section_border_padding_horizontal
end

function log_browser.update(State, dt)
end

function log_browser.quit(State)
end

function log_browser.mouse_pressed(State, x,y, mouse_button)
  local line_index = log_browser.line_index(State, x,y)
  if line_index == nil then
    -- below lower margin
    return
  end
  -- leave some space to click without focusing
  local line = State.lines[line_index]
  local xleft = log_browser.left_margin(State, line)
  local xright = log_browser.right_margin(State, line)
  if x < xleft or x > xright then
    return
  end
  -- if it's a section begin/end and the section is collapsed, expand it
  -- TODO: how to collapse?
  if line.section_begin or line.section_end then
    -- HACK: get section reference from next/previous line
    local new_section
    if line.section_begin then
      if line_index < #State.lines then
        local next_section_stack = State.lines[line_index+1].section_stack
        if next_section_stack then
          new_section = next_section_stack[#next_section_stack]
        end
      end
    elseif line.section_end then
      if line_index > 1 then
        local previous_section_stack = State.lines[line_index-1].section_stack
        if previous_section_stack then
          new_section = previous_section_stack[#previous_section_stack]
        end
      end
    end
    if new_section and new_section.expanded == nil then
      new_section.expanded = true
      return
    end
  end
  -- open appropriate file in source side
  if line.filename ~= Editor_state.filename then
    source.switch_to_file(line.filename)
  end
  -- set cursor
  Editor_state.cursor1 = {line=line.line_number, pos=1, posB=nil}
  -- make sure it's visible
  -- TODO: handle extremely long lines
  Editor_state.screen_top1.line = math.max(0, Editor_state.cursor1.line-5)
  -- show cursor
  Focus = 'edit'
  -- expand B side
  Editor_state.expanded = true
end

function log_browser.line_index(State, mx,my)
  -- duplicate some logic from log_browser.draw
  local y = State.top
  for line_index = State.screen_top1.line,#State.lines do
    local line = State.lines[line_index]
    if should_show(line) then
      y = y + log_browser.height(State, line_index)
      if my < y then
        return line_index
      end
      if y > App.screen.height then break end
    end
  end
end

function log_browser.mouse_released(State, x,y, mouse_button)
end

function log_browser.textinput(State, t)
end

function log_browser.keychord_pressed(State, chord, key)
  -- move
  if chord == 'up' then
    while State.screen_top1.line > 1 do
      State.screen_top1.line = State.screen_top1.line-1
      if should_show(State.lines[State.screen_top1.line]) then
        break
      end
    end
  elseif chord == 'down' then
    while State.screen_top1.line < #State.lines do
      State.screen_top1.line = State.screen_top1.line+1
      if should_show(State.lines[State.screen_top1.line]) then
        break
      end
    end
  elseif chord == 'pageup' then
    local y = 0
    while State.screen_top1.line > 1 and y < App.screen.height - 100 do
      State.screen_top1.line = State.screen_top1.line - 1
      if should_show(State.lines[State.screen_top1.line]) then
        y = y + log_browser.height(State, State.screen_top1.line)
      end
    end
  elseif chord == 'pagedown' then
    local y = 0
    while State.screen_top1.line < #State.lines and y < App.screen.height - 100 do
      if should_show(State.lines[State.screen_top1.line]) then
        y = y + log_browser.height(State, State.screen_top1.line)
      end
      State.screen_top1.line = State.screen_top1.line + 1
    end
  end
end

function log_browser.height(State, line_index)
  local line = State.lines[line_index]
  if line.data == nil then
    -- section header
    return State.line_height
  elseif type(line.data) == 'string' then
    return State.line_height
  else
    if line.height == nil then
--?       print('nil line height! rendering off screen to calculate')
      line.height = log_render[line.data.name](line.data, State.left, App.screen.height, State.right-State.left)
    end
    return line.height
  end
end

function log_browser.keyreleased(State, key, scancode)
end