
.13.e.source-code.os.scala Maven / Gradle / Ivy
/*
Copyright 2010 Aaron J. Radke
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// vim: set ts=3 sw=3 et:
package cc.drx
import scala.sys.process.Process
import java.io.OutputStream
object OS{
//helper function for OS kinss for version maps (keeps the compile tree small)
private def versionMap(str:String):Map[Version,String] = str.split(',').map{d =>
val List(k,v) = d.split('=').toList
Version(k) -> v
}.toMap
//--os types (Kind)
sealed trait Kind {
//--required
def name:String
//--overidable
def codenames:Map[Version,String] = Map.empty[Version,String]
//--overidable
def codename:String = codenames.get(OS.version take 2) getOrElse ""
final override def toString:String = if(codename == "") name else name + codename
}
//--Windows (Kind)
case object Windows extends Kind {
val name = "Windows"
//https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions
override lazy val codenames = versionMap("10.0=10,6.3=8.1,6.2=8,6.1=7,6.0=Vista,5.2=XP Pro,5.1=XP,4.9=ME,5.0=2000,4.10=98")
}
//--Unix (Kind)
sealed trait Unix extends Kind
case object MacOS extends Unix{
val name = "MacOS"
// https://en.wikipedia.org/wiki/MacOS
override lazy val codenames = versionMap("10.7=Lion,10.8=Mountain Lion,10.9=Mavericks,10.10=Yosemite,10.11=El Capitan,10.12=Sierra,10.13=High Sierra,10.14=Mojave,10.15=Catalina")
}
case object Linux extends Unix{
val name = "MacOS"
}
//--Unknown (Kind)
case object Unknown extends Kind{
val name = "Unknown"
}
//--property getters
def prop(key:String):String = System.getProperty(key) //FIXME thee are dangerous null producers
def env(key:String):String = System.getenv(key)
lazy val Env = StringMap(scala.sys.env)
lazy val Prop = StringMap(scala.sys.props.toMap) //make the bidirectional mutable map immutable at the point of eval
lazy val Conf = StringMap(scala.sys.env ++ scala.sys.props.toMap)
//--runtime java and scala versions TODO shouldn't this be under the version constructor?
lazy val javaVersion:Version = Version(prop("java.version"))
lazy val java7:Boolean = javaVersion.similar(Version("1.7.x"),2)
lazy val java8:Boolean = javaVersion.similar(Version("1.8.x"),2)
lazy val java9:Boolean = javaVersion.similar(Version("1.9.x"),2)
lazy val java10:Boolean = javaVersion.similar(Version("10.x"),1)
lazy val java11:Boolean = javaVersion.similar(Version("11.x"),1)
lazy val java12:Boolean = javaVersion.similar(Version("12.x"),1)
lazy val java13:Boolean = javaVersion.similar(Version("13.x"),1)
lazy val scalaVersion:Version = Version(scala.util.Properties.versionString.dropWhile(!_.isDigit))
lazy val scala213:Boolean = scalaVersion.similar(Version("2.13.x"), 2)
lazy val scala212:Boolean = scalaVersion.similar(Version("2.12.x"), 2)
lazy val scala211:Boolean = scalaVersion.similar(Version("2.11.x"), 2)
lazy val scala210:Boolean = scalaVersion.similar(Version("2.10.x"), 2)
//TODO
lazy val dotty:Boolean = ???
lazy val graalNative:Boolean = ???//vm == "Graal??" //java.vm.name
lazy val graalJVM:Boolean = vm startsWith "GraalVM" //java.vm.name
lazy val scalaJVM:Boolean = vm startsWith "Java HotSpot" //java.vm.name
lazy val scalaJS:Boolean = vm startsWith "Scala.js" //java.vm.name.
lazy val scalaNative:Boolean = vm startsWith "Scala Native" //java.vm.name
// lazy val drx:Version = ???
//--special properties
lazy val name = prop("os.name")
lazy val vm = prop("java.vm.name")
lazy val arch = prop("os.arch")
// lazy val bit:Int = prop/"os.arch.data.model" | 64 //default 64 FIXME lookup arch name to get actual bits
lazy val version = Version(prop("os.version"))
lazy val user = prop("user.name")
lazy val pwd:File = Prop / "user.dir" | File(".").canon
lazy val home = File(prop("user.home"))
lazy val kind:Kind = {
val n = name.toLowerCase
if(n contains "mac") MacOS
else if(n contains "win") Windows
else if(n contains "linux") Linux
else Unknown
}
lazy val isMac = kind == MacOS
lazy val isWin = kind == Windows
lazy val isLinux = kind == Linux
//--fantastic robust detection trick from https://stackoverflow.com/a/22039095/622016
private lazy val traceClasses = new Exception().getStackTrace.map{_.getClassName}
lazy val isSbt = traceClasses contains "sbt.Run"
lazy val isBt = traceClasses contains "xsbt.boot.Boot"
lazy val isLauncher= isSbt || isBt
private def pathList(path:String):List[File] = (
path.trim split prop("path.separator") map {_.trim} filter (_ != "") map File.apply map {_.canon}
).toList
lazy val path:List[File] = pathList( prop("java.library.path") )
lazy val classpath:List[File] = pathList( prop("java.class.path") )
lazy val bootpath:List[File] = pathList( prop("sun.boot.library.path") )
lazy val bootClasspath:List[File] = pathList( prop("sun.boot.class.path") )
def where(basename:String) = for(p <- path; f <- p.list; if f.isFile && f.basename == basename) yield f
private lazy val runtime = java.lang.Runtime.getRuntime
lazy val cpus = runtime.availableProcessors
lazy val bit = System.getProperty("sun.arch.data.model").toInt
case class Memory(free:Bytes,total:Bytes,max:Bytes){
lazy val used:Bytes = total - free
}
def heap = Memory(Bytes(runtime.freeMemory), Bytes(runtime.totalMemory), Bytes(runtime.maxMemory))
// lazy val info:String = Macro.ksonColor(kind.name,arch,version,javaVersion,scalaVersion,heap.max,cpus,bit,user)
lazy val info:String =
List("name"->kind.name, "arch"->arch, "version"->version, "java"->javaVersion, "scala"->scalaVersion, "heap"->heap.max, "cpus"->cpus, "bit"->bit, "user"->user, "codename" -> kind.codename)
.map{case (k,v) => k.toString -> v.toString}.map(Format.kvColor).mkString(" ")
def drive = File.roots mapWith {_.driveSize}
private def wmic(domain:String, keys:Seq[String]):Map[String,String] = {
import Implicit.ec
lazy val KeyValue = """(\w+)=(.+)""".r
Shell("wmic",domain,"get",keys mkString ",", "/format:list").lines.blockWithoutWarning(1.s).getOrElse(Nil).collect{
case KeyValue(k,v) => k -> v
}.toMap
}
def ram:Option[Memory] = kind match {
case Windows =>
val keys = Vector("FreePhysicalMemory","TotalVisibleMemorySize","TotalVirtualMemorySize")
val kvs = wmic("os", keys).map{case (k,v) => k -> Bytes(v.toInt)}.toMap
for(free <- kvs get keys(0); total <- kvs get keys(1); max <- kvs get keys(2)) yield Memory(free, total, max)
case _:Unix => None //TODO add an implementation
case _ => None
}
//--launchers
private lazy val desktop = java.awt.Desktop.getDesktop
def open(f:File) = desktop.open(f.canon.file)
def edit(f:File) = desktop.edit(f.canon.file)
def browse(f:File) = desktop.browse(f.canon.file.toURI)
def browse(uri:String) = desktop.browse(new java.net.URI(uri))
def open(uri:String) = browse(uri)
//--process kill
//this Pid class acts as the common interface between linux and windows for 'ps' & 'kill'
case class Pid(pid:Int, cmd:String){
def kill = OS.kill(pid)
}
//TODO replace the process calls with the Shell object type at all??
/**this (blocking) run is a simple way to use the cc.drx.Shell utility but but make several simple choices
* 1.) blocks for 1.min ??? this is bad
* 2.) unwraps the future and the try (returns an empty list in failure cases
* 3.) automatically chooses an global executionContext
* 4.) easy api for adding working directory locatiou
*/
// def run(cmd:String, cwd:File):List[String] = Shell(cmd,Some(cwd),List.empty).run(Implicit.ec).block(1.minute).getOrElse(Nil)
// def run(cmd:String):List[String] = Shell(cmd).run(Implicit.ec).block(1.minute).getOrElse(Nil)
// def run(cmd:String):Future[List[String]] = Shell(cmd).lines
private lazy val WMICPid = """[^,]*,([^,]*),(\d+)""".r
private lazy val PSPid = """\s*\d+\s+(\d+)\s+(.*)""".r
def ps:Iterator[Pid] = kind match {
case Windows =>
for(WMICPid(cmd,pid) <- Process("""wmic process get commandline,processid /format:csv""").lineIterator) yield Pid(pid.toInt,cmd)
case _:Unix =>
for(PSPid(pid,cmd) <- Process(s"ps -u $user").lineIterator) yield Pid(pid.toInt,cmd)
case _ => ???
}
//convenience functions to filter processes searching for a key within a process
def ps(key:String):Vector[Pid] = (for(p <- ps if p.cmd contains key ) yield p).toVector
//convinence functions to provide a jps like solution for systems without jps installed
def jps:Vector[Pid] = (for(p <- ps if p.cmd.toLowerCase contains "java") yield p).toVector
def jps(key:String):Vector[Pid] = (for(p <- jps if p.cmd contains key) yield p).toVector
//Note: this requires the jps command from the jdk on the PATH
def kill(pid:Int):Unit = kind match {
case Windows => Process(s"taskkill /F /PID $pid").!!; {}
case _:Unix => Process(s"kill -9 $pid").!!; {}
case _ => println("Warning: The os must be known")
}
def kill(pid:Pid):Unit = pid.kill
def kill(key:String):Unit = ps(key) foreach kill
def jkill(key:String):Unit = jps(key) foreach kill
//private lazy val toolkit = java.awt.Toolkit.getDefaultToolkit
//def isCapsLock = toolkit.getLockingKeyState(java.awt.event.KeyEvent.VK_CAPS_LOCK)
//def isNumLock = toolkit.getLockingKeyState(java.awt.event.KeyEvent.VK_NUM_LOCK)
//
import Implicit.ec
//-- terminal info
/**use tput to get the current terminal size http://stackoverflow.com/a/263900/622016 ; only works for unix*/
def terminalSize:Future[Vec] = for(
cols <- Shell("tput cols").resultString;
rows <- Shell("tput lines").resultString
) yield Vec(cols.toInt,rows.toInt)
}
object Clipboard{
import java.awt.datatransfer._
private lazy val clipboard = java.awt.Toolkit.getDefaultToolkit.getSystemClipboard
override def toString = Try(clipboard.getData(DataFlavor.stringFlavor).toString) getOrElse ""
def :=(str:String) = {Try{
val selection = new StringSelection(str)
clipboard.setContents(selection,selection)
}; this}
def +=(str:String) = {this := toString + str; this} //by return this, we can do folds to fill the clipboard
def println(str:String) = this += str + "\n"
/**set the clipboard value to the emptyString*/
def empty = {this := ""; this}
def apply(str:String) = this := str
def apply(func: String => Unit) = {
clipboard.addFlavorListener(new FlavorListener(){
override def flavorsChanged(e:FlavorEvent) = func(toString)
})
clipboard
}
/**clear all active clipboard listeners*/
def clear = for(f <- clipboard.getFlavorListeners) clipboard removeFlavorListener f//removal of all flavor listeners
}
object Display{
//def mc = mouseInfo.getDevice.getDefaultConfiguration
def currentGC = java.awt.MouseInfo.getPointerInfo.getDevice.getDefaultConfiguration
def insets(gc:java.awt.GraphicsConfiguration=currentGC):Display = {
val insets = java.awt.Toolkit.getDefaultToolkit.getScreenInsets(gc)
val bounds = gc.getBounds
val loc = Vec(bounds.x+insets.left, bounds.y+insets.top)
val size = Vec(
bounds.width - insets.right - insets.left,
bounds.height - insets.top - insets.bottom
)
Display(loc, size)
}
def fullScreen(gc:java.awt.GraphicsConfiguration=currentGC):Display = {
val bounds = gc.getBounds
val loc = Vec(bounds.x, bounds.y)
val size = Vec(bounds.width, bounds.height)
Display(loc, size)
}
def allScreens:Display = {
val ge = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment
val bounds = ge.getScreenDevices.map{_.getDefaultConfiguration.getBounds}
val minX = bounds.map{b => b.x}.min
val minY = bounds.map{b => b.y}.min
val maxX = bounds.map{b => b.x + b.width}.max
val maxY = bounds.map{b => b.y + b.height}.max
Display(Vec(minX,minY), Vec(maxX-minX, maxY-minY))
}
//TODO move this screen shot to the display class so sub rect can be selected
import java.awt.image.BufferedImage
import java.awt.{Toolkit, Rectangle}
private lazy val robot = new java.awt.Robot()
def screenshotImage:BufferedImage = robot.createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
def screenshot(file:File):Try[File] = file.mkParents.flatMap{file =>
Try{ javax.imageio.ImageIO.write(screenshotImage, file.ext, file); file }
}
def screenshot:Try[File] = screenshot(file"export/screen-${Date.now.krypton.ms}.png")
}
case class Display(loc:Vec, size:Vec)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy