From 2fbe02f7cbb620194669b2eebc1c77a7100195af Mon Sep 17 00:00:00 2001
From: Jiri Kalvoda <jirikalvoda@kam.mff.cuni.cz>
Date: Fri, 12 Aug 2022 15:28:13 +0200
Subject: [PATCH] Add OnClick

---
 Program.cs | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 120 insertions(+), 6 deletions(-)

diff --git a/Program.cs b/Program.cs
index 807628a..457b63d 100644
--- a/Program.cs
+++ b/Program.cs
@@ -47,6 +47,17 @@ static class POSIX
 	public static int SIGUSR1 = 10;
 	public static int EEXIST = 17;
 	public static int ERRNO { get => Marshal.GetLastPInvokeError();}
+	public static void Bash(string cmd)
+	{
+		var process = new Process();
+		process.StartInfo.FileName = "bash";
+		process.StartInfo.Arguments = "";
+		process.StartInfo.RedirectStandardInput = true;
+		process.Start();
+		TextWriter stdin = process.StandardInput;
+		stdin.Write(cmd);
+		stdin.Close();
+	}
 }
 
 static class ProcesExtended
@@ -97,6 +108,33 @@ enum Markup
 	Pango
 }
 
+record Coordinates(
+		int X,
+		int Y
+		);
+
+enum MouseButton
+{
+	Left,
+	Right,
+	Middle
+}
+
+[Flags]
+enum Modifiers
+{
+	Shift=1,
+	Ctrl=2
+}
+
+record ClickEvent(
+		Coordinates Relative, // relative_x, relative_y
+		Coordinates Size, // width, height
+		MouseButton Button,
+		Coordinates? Absolute = null, // x, y
+		Coordinates? Output = null, // output_x, output_y
+		Modifiers Modifiers = 0
+		);
 
 record Block(
 		string Text,
@@ -114,7 +152,8 @@ record Block(
 		bool Urgent = false,
 		bool Separator = true,
 		int? SeparatorBlockWidth=null,
-		Markup Markup = Markup.None
+		Markup Markup = Markup.None,
+		Action<ClickEvent>? OnClick=null
 	);
 
 
@@ -417,14 +456,82 @@ class StatusBarTerminal: StatusBarPlainText
 }
 class StatusBarI3: RootStatusBar
 {
-	public StatusBarI3(FileInfo configFile):base(configFile){}
+	Thread? inputThread;
+	record HistoryElement(
+			long time_ms,
+			long id,
+			IEnumerable<Block> data
+			);
+	Queue<HistoryElement> history = new(); // for finding correct lambda in inputThread
+	int outputCounter = 0;
+	public StatusBarI3(FileInfo configFile, bool doInput):base(configFile)
+	{
+		Console.Error.Flush();
+		if(doInput)
+		{
+			inputThread = new Thread(this.inputThreadFunc);
+			inputThread.IsBackground = true;
+			inputThread.Start();
+		}
+	}
+	void inputThreadFunc()
+	{
+		Console.ReadLine(); // read "["
+		while(true)
+		{
+			#pragma warning disable 8602
+			string? line = Console.ReadLine();
+			if(line == null) throw new Exception("I3bar close input");
+			if(line[0] == ',') line = line[1..];
+
+			JsonObject json = JsonObject.Parse(line).AsObject();
+			MouseButton button;
+			int jsonButton = json["button"].AsValue().GetValue<int>();
+			if(jsonButton == 1) button = MouseButton.Left; else
+			if(jsonButton == 2) button = MouseButton.Middle; else
+			if(jsonButton == 3) button = MouseButton.Left; else
+				break;
+			var ev = new ClickEvent(
+					Relative: new Coordinates(json["relative_x"].AsValue().GetValue<int>(), json["relative_y"].AsValue().GetValue<int>()),
+					Size: new Coordinates(json["height"].AsValue().GetValue<int>(), json["width"].AsValue().GetValue<int>()),
+					Button: button
+					);
+			string name = json["name"].AsValue().GetValue<string>();
+			int outputId = int.Parse(name.Split(".")[0]);
+			int blockId = int.Parse(name.Split(".")[1]);
+			HistoryElement currentBar;
+			lock(history)
+			{
+				while(history.Count > 0 && history.Peek().id < outputId)
+					history.Dequeue();
+				if(history.Count <= 0) break;
+				currentBar = history.Peek();
+			}
+			var block = currentBar.data.ElementAt(blockId);
+			if(block.OnClick != null)
+				block.OnClick(ev);
+			#pragma warning restore 8602
+		}
+	}
 	override protected void initOutput(TextWriter w)
 	{
-		w.WriteLine("{\"version\":1}");
+		if(inputThread == null)
+			w.WriteLine("{\"version\":1}");
+		else
+			w.WriteLine("{\"version\":1, \"click_events\": true }");
 		w.WriteLine("[{}");
 	}
 	override protected void format(TextWriter w, List<Block> elements)
 	{
+		if(inputThread != null)
+		{
+			lock(history)
+			{
+				while(history.Count > 0 && history.Peek().time_ms < Environment.TickCount64 - 1000)
+					history.Dequeue();
+				history.Enqueue(new HistoryElement(Environment.TickCount64, outputCounter, elements));
+			}
+		}
 		JsonNode? intStringUnion(int? _int, string? _string)
 		{
 			JsonNode? n = null;
@@ -432,7 +539,9 @@ class StatusBarI3: RootStatusBar
 			if(_string != null) n = _string;
 			return n;
 		}
+		int i = 0;
 		var json = new JsonArray((from e in elements select new JsonObject(){
+			["name"] = $"{outputCounter}.{i++}",
 			["full_text"] = e.Text,
 			["short_text"] = e.ShortText,
 			["color"] = e.Color?.ToHex(),
@@ -453,6 +562,7 @@ class StatusBarI3: RootStatusBar
 		opt.DefaultIgnoreCondition =  System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull;
 		w.Write(",");
 		w.WriteLine(json.ToJsonString(opt));
+		outputCounter++;
 	}
 }
 
@@ -504,10 +614,14 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.");
 
 		var c_i3 = new Command("i3", "Comunicate with i3bar.");
 		rootCommand.Add(c_i3);
-		c_i3.SetHandler(async (config) =>
+		var c_i3_input = new Option<bool>
+			("--input", "Read mouse clicks.");
+		c_i3_input.AddAlias("-i");
+		c_i3.Add(c_i3_input);
+		c_i3.SetHandler(async (config, input) =>
 				{
-					(new StatusBarI3(config)).Run(Console.Out);
-				}, configOption);
+					(new StatusBarI3(config, input)).Run(Console.Out);
+				}, configOption, c_i3_input);
 
 		rootCommand.Invoke(args);
 		return 0;
-- 
GitLab