import std.conv; import std.datetime; import std.experimental.logger; import std.stdio; import std.string; import vibe.core.core; import vibe.core.net; import vibe.core.process; import ddbus; import ddbus.c_lib; version(WithRPC) import msgpackrpc; string appName = "Automatisch In- en Uitschakelen Ober"; extern (C) void close(int fileNo); /** * Represententeerd de toepassing */ struct Toep { public: /** * Tijd die tussen het schakelen van GebruikerActief -> SpelerActief en SpelerActief -> Uit * moet zitten. */ immutable float m_waitingTime; /// Aantal keer dat moet worden gecontroleerd of er een gebruiker actief is. immutable int CHECK_COUNT = 4; Connection m_dbusCon; bool m_keepRunning = true; enum Staat { Inactief, /// Een (Unix-)gebruiker is actief op het rekentuig GebruikersActief, /// Een speler is actief op de mijnvervaardigingsober SpelersActief, /// Het rekentuig moet uit Uit } this(float waitingTime) { this.m_waitingTime = waitingTime / CHECK_COUNT; this.m_dbusCon = connectToBus(DBusBusType.DBUS_BUS_SYSTEM); } version(WithRPC) { Client m_client; immutable string m_clientAddress; immutable ushort PORT = 18_002; this(float waitingTime, string clientAddress) { this.m_waitingTime = waitingTime / CHECK_COUNT; this.m_clientAddress = clientAddress; this.m_dbusCon = connectToBus(DBusBusType.DBUS_BUS_SYSTEM); } } /** * Voer de toepassing uit en treed de gebeurtenissenlus binnen. * * return: Afsluitcode die geretourneerd moet worden binnen main() */ int exec() { info ("Gebruikers actief:", gebruikersActief()); info ("Spelers actief:", spelersActief()); version(WithRPC) { TCPConnection conn = connectTCP(m_clientAddress, PORT); Duration sleepTime = 500.msecs; while(!conn) { infof("Niet verbonden, opnieuw proberen in %s", sleepTime); sleep(sleepTime); conn = connectTCP(m_clientAddress, PORT); } info("Verbonden!"); m_client = new Client(conn); writeln("Antwoord ontvangen: '", m_client.call!(string)("GetBootReason"), "'"); } // Start de D-Bus-gebeurtenissenlus. runTask({ import vibe.core.core : yield; while(m_dbusCon.tick() && m_keepRunning) { yield(); } }); // Start de gebeurtenissenlus voor de hoofdlogica. auto taak = runTask({ FileDescriptor inhibitLock = FileDescriptor.none; /+version(WithRPC) { FileDescriptor sleepLock = FileDescriptor.none; FileDescriptor shutdownLock = FileDescriptor.none; }+/ // Get interfaces BusName loginBus = busName("org.freedesktop.login1"); ObjectPath loginPath = ObjectPath("/org/freedesktop/login1"); InterfaceName loginIFace = interfaceName("org.freedesktop.login1.Manager"); PathIface loginManager = new PathIface(m_dbusCon, loginBus, loginPath, loginIFace); PathIface loginManagerProps = new PathIface(m_dbusCon, loginBus, loginPath, interfaceName("org.freedesktop.DBus.Properties")); /// Laat een slot los. void releaseLock(ref FileDescriptor fd) { if (fd != FileDescriptor.none) { close(cast(int) fd); fd = FileDescriptor.none; } } version(WithRPC) { // Register signal listeners // FIXME: this does not work yet. /+MessageRouter router = new MessageRouter(); MessagePattern sleepPattern = MessagePattern(loginPath, loginIFace, "PrepareForSleep", true); MessagePattern shutdownPattern = MessagePattern(loginPath, loginIFace, "PrepareForShutdown", true); router.setHandler!(void, bool)(sleepPattern, (bool active) { logf("Preparing for sleep: %b", active); if (active) { m_client.notify("NotifySleep"); releaseLock(sleepLock); } else { m_client.notify("NotifyWakeup"); sleepLock = loginManager.Inhibit("sleep", appName, "Systeem op de hoogte brengen dat de ober gaat slapen", "delay").to!FileDescriptor; } }); router.setHandler!(void, bool)(shutdownPattern, (bool active) { logf("Preparing for shutdown: %b", active); if (active) { m_client.notify("NotifyShutdown"); releaseLock(shutdownLock); } }); registerRouter(m_dbusCon, router); // Take the sleep lock sleepLock = loginManager.Inhibit("sleep", appName, "Systeem op de hoogte brengen dat de ober gaat slapen", "delay").to!FileDescriptor; shutdownLock = loginManager.Inhibit("shutdown", appName, "Systeem op de hoogte brengen dat de ober gaat sluiten", "delay").to!FileDescriptor;+/ } void blokkeerAfsluiten() { if (inhibitLock != FileDescriptor.none) return; Message mesg = loginManager.Inhibit("shutdown:sleep:idle:handle-suspend-key", appName, "Er zijn spelers op de ober", "block"); inhibitLock = mesg.to!FileDescriptor; } scope (exit) { // Als we om een of andere redenen deze functie verlaten, laat het slot los! releaseLock(inhibitLock); version(WithRPC) { /+releaseLock(sleepLock); releaseLock(shutdownLock);+/ } } // Teller die bijhoudt wanneer we bepaalde van bepaalde staten mogen wisselen. Staat staat = Staat.Inactief; int teller = CHECK_COUNT; while(m_keepRunning) { Staat oudeStaat = staat; sleep(seconds(cast(long) (m_waitingTime * 60))); // Staattransities die vanuit elke staat kunnen plaatsvinden. if (spelersActief()) { staat = Staat.SpelersActief; } final switch(staat) { case Staat.Inactief: if (spelersActief()) { staat = Staat.SpelersActief; teller = 3; } else if (gebruikersActief) { staat = Staat.GebruikersActief; teller = 3; } else if (--teller <= 0) { sluitAf(); staat = Staat.Uit; } break; case Staat.GebruikersActief: if (spelersActief()) { teller = 3; staat = Staat.SpelersActief; } else if (gebruikersActief()) { teller = 3; } else if (--teller <= 0) { staat = Staat.Uit; sluitAf(); } break; case Staat.SpelersActief: try { blokkeerAfsluiten(); } catch(DBusException e) { warning("Kon afsluitslot niet verkrijgen: ", e); } if (spelersActief()) { teller = 3; } else if (--teller <= 0) { releaseLock(inhibitLock); staat = Staat.GebruikersActief; teller = 3; } break; case Staat.Uit: info("We gaan dit programma afsluiten"); m_keepRunning = false; exitEventLoop(); return; } tracef("Oude -> nieuwe staat: %s -> %s (teller: %d)", oudeStaat, staat, teller); } }); int exitCode = runEventLoop(); // Rommel opruimen info("Rommel opruimen."); return exitCode; } /** * Kijkt of we het afsluiten moeten blokkeren, oftewel, of we naar de SpelerActief-staat moeten. */ bool spelersActief() { // Kijk of er verbindingen actief zijn naar poort ":minecraft" auto resultaat = execute(["ss", "-H", "--query=tcp", "state", "established", "sport", "=", ":minecraft"]); return resultaat.output.splitLines().length != 0; } /** * Kijkt of we direct af kunnen sluiten, oftewel dat we naar de Uit-staat mogen. */ bool gebruikersActief() { auto resultaat = execute(["who"]); return resultaat.output.splitLines().length == 0; } void sluitAf() { version(WithRPC) m_client.notify("NotifyShutdown"); execute(["shutdown", "+1", "Systeem gaat afsluiten vanwege inactiviteit"]); } } int main(string[] args) { version(WithRPC) { scope(failure) stderr.writefln("GEBRUIK: %s WACHTTIJD KLANTADRES", args[0]); if (args.length < 3) { stderr.writefln("GEBRUIK: %s WACHTTIJD KLANTADRES", args[0]); return -1; } } else { scope(failure) stderr.writefln("GEBRUIK: %s WACHTTIJD", args[0]); if (args.length < 2) { stderr.writefln("GEBRUIK: %s WACHTTIJD", args[0]); return -1; } } float waitingTime = to!float(args[1]); version(WithRPC) { return Toep(waitingTime, args[2]).exec(); } else { return Toep(waitingTime).exec(); } }