Skip to content
Snippets Groups Projects
Select Git revision
  • 72543e916b660cf66ae29864eb962f3b2b71bb0d
  • master default
2 results

Program.cs

  • Program.cs 13.89 KiB
    #nullable enable
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    using System.IO;
    
    using Pastel;
    using System.Text.Json;
    using System.Text.Json.Nodes;
    using System.Linq;
    using System.Threading;
    
    using System.CommandLine;
    using System.Runtime.InteropServices;
    
    using Color = System.Drawing.Color;
    using Process = System.Diagnostics.Process;
    
    using Config;
    
    namespace i3csstatus {
    	static class ProcesExtended
    	{
    		[DllImport("libc", SetLastError = true)]
    		public static extern int kill(int pid, int sig);
    		public static int KillBySignal(this Process p, int sig)
    		{
    			return kill(p.Id, sig);
    		}
    	}
    	static class JsonExtended
    	{
    		public static JsonObject RemoveNull(this JsonObject o)
    		{
    			List<string> toDelete = new();
    			foreach(var (k, v) in o)
    			{
    				if(v == null)
    					toDelete.Add(k);
    			}
    			foreach(var k in toDelete)
    				o.Remove(k);
    			return o;
    		}
    	}
    	static class ColorToHexExtended
    	{
    		static public string ToHex(this Color c)
    		{
    			return $"#{c.R:X2}{c.G:X2}{c.B:X2}";
    		}
    	}
    	record Element(
    			string text,
    			string? short_text=null,
    			Color? color=null
    		);
    
    	class ModuleException: Exception
    	{
    	}
    	[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)]
    	public class ModuleName : System.Attribute
    	{
    		string name;
    		public ModuleName(string _name)
    		{
    			name = _name;
    		}
    		public string GetName() => name;
    	}
    	class GlobalModuleResource
    	{
    		public virtual void InitEnd(){}
    		public virtual void GetBegin(){}
    		public virtual void GetEnd(){}
    	}
    	#nullable disable
    	interface Module
    	{
    		IEnumerable<Element> Get();
    		void Init(StatusBar _bar, ConfigParser config, ConfigSection section);
    	}
    	[ModuleName("constant")]
    	class ModuleConstant: Module
    	{
    		string text;
    		Color color;
    		public void Init(StatusBar _bar, ConfigParser config, ConfigSection section)
    		{
    			text = section.Mandatory("text");
    			color = section?["color"]?.AsColor() ?? System.Drawing.ColorTranslator.FromHtml("white");
    		}
    		public IEnumerable<Element> Get()
    		{
    			return new Element[]{new Element(text, color: color)};
    		}
    	}
    	[ModuleName("file")]
    	class ModuleFile: Module
    	{
    		string path;
    		protected Color color;
    		InnerStatusBar ifNotFound;
    		InnerStatusBar ifReadError;
    		public void Init(StatusBar _bar, ConfigParser config, ConfigSection section)
    		{
    			path = section.Mandatory("path").AsPath();
    			color = section.Optional("color")?.AsColor() ?? System.Drawing.ColorTranslator.FromHtml("white");
    			ifNotFound  = new InnerStatusBar(_bar, section.Optional("not_found")?.AsConfig() ??
    					section.Optional("read_error")?.AsConfig() ??
    					new ConfigParser(
    @"
    [constant]
    color = red
    text = NFound
    "));
    			ifReadError  = new InnerStatusBar(_bar, section.Optional("read_error")?.AsConfig() ??
    					new ConfigParser(
    @"
    [constant]
    color = red
    text = ERR
    "));
    		}
    		protected virtual IEnumerable<Element> Parse(string text)
    		{
    			if(text.Length > 0 && text[^1] == '\n') text = text[..^1];
    			return text.Split("\n").Select(x => new Element(x, color: color)).ToArray();
    		}
    		public IEnumerable<Element> Get()
    		{
    			string text;
    			try
    			{
    				text = File.ReadAllText(path);
    			}
    			catch (Exception ex) when (ex is FileNotFoundException || ex is DirectoryNotFoundException)
    			{
    				return ifNotFound.Get();
    			}
    			catch (Exception)
    			{
    				return ifReadError.Get();
    			}
    			return Parse(text);
    		}
    	}
    	[ModuleName("osdd_last")]
    	class ModuleOsddLast: ModuleFile
    	{
    		public new void Init(StatusBar _bar, ConfigParser config, ConfigSection section)
    		{
    			base.Init(_bar, config, section);
    		}
    		static string timeShow(long t_s)
    		{
    			if(t_s<180) return $"{t_s}s";
    			if(t_s<180*60) return $"{t_s/60}min";
    			return $"{t_s/60/60}h";
    		}
    		protected override IEnumerable<Element> Parse(string text)
    		{
    			try
    			{
    				var lines = text.Split("\n");
    				List<Element> output = new();
    				long timeEleapsed_s = DateTimeOffset.Now.ToUnixTimeSeconds()-long.Parse(lines[0]);
    				bool first = true;
    				foreach(var l in lines[1..])
    				{
    					if(l.Length > 7)
    						output.Add(new Element((first?$"[{timeShow(timeEleapsed_s)}] ":"") + l[7..], color: System.Drawing.ColorTranslator.FromHtml("#"+l[..6])));
    					first = false;
    				}
    				return output;
    			}
    			catch(Exception)
    			{
    				return new Element[]{new Element("OSDD parse ERROR", color: Color.Red)};
    			}
    		}
    	}
    	[ModuleName("i3status")]
    	class ModuleI3Status: Module
    	{
    		class ModuleI3StatusException: ModuleException
    		{
    			public override string ToString()
    			{
    				return $"I3Status don't work correctly.";
    			}
    		}
    		string name;
    		string configuration;
    		Element[] elements;
    		class Global: GlobalModuleResource
    		{
    			public List<ModuleI3Status> modules = new();
    			public Dictionary<string, ModuleI3Status> moduleByName = new();
    			System.Diagnostics.Process process;
    			public int? cache_ms;
    			long lastGet_ms = 0;
    			public override void InitEnd()
    			{
    				process = new();
    				//process.StartInfo.FileName = "tail";
    				//process.StartInfo.Arguments = "-n 5";
    				process.StartInfo.FileName = "i3status";
    				process.StartInfo.Arguments = "-c /dev/stdin";
    				process.StartInfo.RedirectStandardInput = true;
    				process.StartInfo.RedirectStandardOutput = true;
    				process.Start();
    				TextWriter stdin = process.StandardInput;
    				//stdin = Console.Out;
    				var stdout = process.StandardOutput;
    				stdin.WriteLine("general {");
    				stdin.WriteLine("output_format = \"i3bar\"");
    				stdin.WriteLine("colors = true");
    				stdin.WriteLine("interval = 1000000000");
    				stdin.WriteLine("}");
    				foreach(var m in modules)
    				{
    					stdin.WriteLine($"order += \"{m.name}\"");
    					stdin.WriteLine($"{m.name} {{");
    					if(m.configuration != null)
    						stdin.WriteLine(m.configuration);
    					stdin.WriteLine($"}}");
    				}
    				stdin.Close();
    				stdout.ReadLine();
    				stdout.ReadLine();
    				stdout.ReadLine();
    			}
    			static Element parseElement(JsonObject json)
    			{
    				return new Element(
    						text: json["full_text"].AsValue().GetValue<string>(),
    						short_text: json["short_text"]?.AsValue()?.GetValue<string>(),
    						color: System.Drawing.ColorTranslator.FromHtml(json["color"]?.AsValue()?.GetValue<string>() ?? "white")
    						);
    			}
    			public override void GetBegin()
    			{
    				long t = Environment.TickCount64;
    				if(lastGet_ms + cache_ms > t)
    					return;
    				lastGet_ms = t;
    				process.KillBySignal(10);
    				string line = process.StandardOutput.ReadLine();
    				if(line == null)
    				{
    					throw new ModuleI3StatusException();
    				}
    				JsonArray json = JsonObject.Parse(line[1..]).AsArray();
    				if(json.Count != modules.Count)
    					throw new Exception("Parse i3status error");
    				for(int i=0;i<modules.Count;i++)
    				{
    					modules[i].elements = new []{parseElement(json[i].AsObject())};
    				}
    			}
    		}
    		public void Init(StatusBar _bar, ConfigParser config, ConfigSection section)
    		{
    			name = section["name"];
    			configuration = section.Optional("config")?.AsString();
    			var g = _bar.GetGlobal<Global>();
    			g.modules.Add(this);
    			if(g.moduleByName.ContainsKey(name))
    				throw new ConfigMistake(section["name"], "Duplicit i3status name.");
    			if(section.Optional("global_cache") != null)
    			{
    				int cache_ms = section["global_cache"].AsMs();
    				if(g.cache_ms != null && g.cache_ms != cache_ms)
    					throw new ConfigMistake(section["global_cache"], "Collision definition of global value.");
    				g.cache_ms = cache_ms;
    			}
    			g.moduleByName[name] = this;
    		}
    		public IEnumerable<Element> Get() => elements ?? new Element[]{};
    	}
    	#nullable restore
    	abstract class StatusBar
    	{
    		List<Module> modules = new();
    		protected void parseConfigFile(FileInfo configFile)
    		{
    			parseConfigString(File.ReadAllText(configFile.ToString()));
    		}
    		protected void parseConfigString(string configString)
    		{
    			parseConfig(new ConfigParser(configString));
    		}
    		virtual protected void parseConfig(ConfigParser p)
    		{
    			var moduleTypes = new Dictionary<string, Type>(
    					from assembly in AppDomain.CurrentDomain.GetAssemblies()
    					from type in assembly.GetTypes()
    					where type.IsDefined(typeof(ModuleName), false)
    					where typeof(Module).IsAssignableFrom(type)
    					from name in type.GetCustomAttributes(typeof(ModuleName), false)
    					select new KeyValuePair<string, Type>(((ModuleName)name).GetName(), type)
    			);
    
    			var constructorSignature = new Type[]{};
    
    			foreach(var s in p)
    			{
    				string type = s["_type"] ?? s.SectionName;
    				var constructor = moduleTypes[type].GetConstructor(constructorSignature);
    				if(constructor == null)
    					throw new Exception($"Missing constructor of {type} module");
    				Module module = (Module) constructor.Invoke(new object[]{});
    				module.Init(this, p, s);
    				modules.Add(module);
    			}
    		}
    		public List<Element> Get()
    		{
    			var elements = new List<Element>();
    			foreach(var m in modules)
    				elements.AddRange(m.Get());
    			return elements;
    		}
    		public abstract void Schedule(int in_ms);
    		public abstract T GetGlobal<T>() where T: GlobalModuleResource, new();
    	}
    	class InnerStatusBar: StatusBar
    	{
    		StatusBar parrent;
    		public InnerStatusBar(StatusBar _parrent, ConfigParser p)
    		{
    			parrent = _parrent;
    			parseConfig(p);
    		}
    		public override void Schedule(int in_ms) => parrent.Schedule(in_ms);
    		public override T GetGlobal<T>() => parrent.GetGlobal<T>();
    	}
    	abstract class RootStatusBar: StatusBar
    	{
    		long nextRun = 0;
    		long refresh_ms;
    		object nextRunMonitor = new();
    		public RootStatusBar(FileInfo configFile)
    		{
    			parseConfigFile(configFile);
    		}
    		override protected void parseConfig(ConfigParser p)
    		{
    			refresh_ms = p.MainSection["refresh"]?.AsMs() ?? 1000;
    
    			base.parseConfig(p);
    
    			foreach(var g in globalModuleResources)
    				g.Value.InitEnd();
    		}
    		void wait()
    		{
    			lock(nextRunMonitor)
    			{
    				while(true)
    				{
    					long actual = Environment.TickCount64;
    					if(nextRun <= actual)
    						break;
    					long wait_ms = Math.Min(10000, nextRun - actual);
    					Monitor.Wait(nextRunMonitor, (int)wait_ms, true);
    				}
    				nextRun = Environment.TickCount64 + refresh_ms;
    			}
    		}
    		public void Run(TextWriter w)
    		{
    			initOutput(w);
    			while(true)
    			{
    				wait();
    				Print(w);
    			}
    		}
    		abstract protected void format(TextWriter w, List<Element> elements);
    		virtual protected void initOutput(TextWriter w){}
    		public void Print(TextWriter w)
    		{
    			foreach(var g in globalModuleResources)
    				g.Value.GetBegin();
    			var elements = Get();
    			foreach(var g in globalModuleResources)
    				g.Value.GetEnd();
    			format(w, elements);
    			w.Flush();
    		}
    		public override void Schedule(int in_ms)
    		{
    			lock(nextRunMonitor)
    			{
    				long actual = Environment.TickCount64;
    				if(nextRun <= actual + in_ms)
    					return;
    				nextRun = actual + in_ms;
    				Monitor.Pulse(nextRunMonitor);
    			}
    		}
    		Dictionary<Type, GlobalModuleResource> globalModuleResources = new();
    		public override T GetGlobal<T>()
    		{
    			if(!globalModuleResources.ContainsKey(typeof(T)))
    				globalModuleResources[typeof(T)] = new T();
    			return (T)globalModuleResources[typeof(T)];
    		}
    	}
    	class StatusBarPlainText: RootStatusBar
    	{
    		public StatusBarPlainText(FileInfo configFile):base(configFile){}
    		override protected void format(TextWriter w, List<Element> elements)
    		{
    			bool first = true;
    			foreach(var e in elements)
    			{
    				if(!first)
    					w.Write("|");
    				first = false;
    				w.Write(e.text);
    			}
    			w.WriteLine("");
    		}
    	}
    	class StatusBarTerminal: RootStatusBar
    	{
    		Thread inputThread;
    		public StatusBarTerminal(FileInfo configFile, bool doInput):base(configFile)
    		{
    			if(doInput)
    			{
    				inputThread = new Thread(this.inputThreadFunc);
    				inputThread.IsBackground = true;
    				inputThread.Start();
    			}
    		}
    		void inputThreadFunc()
    		{
    			while(true)
    			{
    				System.ConsoleKeyInfo k = Console.ReadKey();
    				if(k.KeyChar == '\r')
    					Schedule(0);
    				else if(k.KeyChar == 'q')
    					Environment.Exit(0);
    				else
    					Console.WriteLine("");
    			}
    		}
    		override protected void format(TextWriter w, List<Element> elements)
    		{
    			bool first = true;
    			foreach(var e in elements)
    			{
    				if(!first)
    					w.Write("|".Pastel(Color.FromArgb(165, 229, 250)));
    				first = false;
    				w.Write(e.color!=null?e.text.Pastel(e.color.Value):e.text);
    			}
    			w.WriteLine("");
    		}
    	}
    	class StatusBarI3: RootStatusBar
    	{
    		public StatusBarI3(FileInfo configFile):base(configFile){}
    		override protected void initOutput(TextWriter w)
    		{
    			w.WriteLine("{\"version\":1}");
    			w.WriteLine("[{}");
    		}
    		override protected void format(TextWriter w, List<Element> elements)
    		{
    			var json = new JsonArray((from e in elements select new JsonObject(){
    				["full_text"] = e.text,
    				["short_text"] = e.short_text,
    				["color"] = e.color?.ToHex(),
    			}.RemoveNull()).ToArray());
    			var opt = new JsonSerializerOptions();
    			opt.DefaultIgnoreCondition =  System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull;
    			w.Write(",");
    			w.WriteLine(json.ToJsonString(opt));
    		}
    	}
    	class Program
    	{
    	#pragma warning disable 1998
    		static int Main(string[] args)
    		{
    			var configOption = new Option<FileInfo>
    				("--config", "Path to configuration file.");
    			configOption.AddAlias("-c");
    			var rootCommand = new RootCommand("An alternative implementation for i3 status bar generating.");
    			rootCommand.AddGlobalOption(configOption);
    
    			var c_plainText = new Command("plain-text", "Print status bar as plain text.");
    			rootCommand.Add(c_plainText);
    			c_plainText.SetHandler(async (config) =>
    					{
    						(new StatusBarPlainText(config)).Run(Console.Out);
    					}, configOption);
    
    			var c_terminal = new Command("terminal", "Print status bar with terminal escape secvence.");
    			rootCommand.Add(c_terminal);
    			var c_terminal_input = new Option<bool>
    				("--input", "Read key input.");
    			c_terminal_input.AddAlias("-i");
    			c_terminal.Add(c_terminal_input);
    			c_terminal.SetHandler(async (config, input) =>
    					{
    						(new StatusBarTerminal(config, input)).Run(Console.Out);
    					}, configOption, c_terminal_input);
    
    			var c_i3 = new Command("i3", "Comunicate with i3bar.");
    			rootCommand.Add(c_i3);
    			c_i3.SetHandler(async (config) =>
    					{
    						(new StatusBarI3(config)).Run(Console.Out);
    					}, configOption);
    
    			rootCommand.Invoke(args);
    			return 0;
    		}
    	#pragma warning restore 1998
    	}
    }