xscreen_usb_unlocker 0.90.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in xscreen_usb_unlocker.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Ernie Brodeur
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # XscreenUsb
2
+
3
+ This gem provides a command line application that will control xscreensaver's lock/unlock features via connecting/disconnecting a given USB device.
4
+
5
+ This will work best with a device that presents a unique serial number, and you have with you all the time . . . like your smart phone. In fact I designed this to be used with an android phone in usb debugging mode (so it will register with the system). I don't have to mount the disk, simply plugging it in is enough.
6
+
7
+ ## Installation
8
+
9
+ $ gem install xscreen_usb_unlocker
10
+
11
+ ## Usage
12
+
13
+ To make sure it's not any device plugged or unpluged, you have to supply either a serial or a device id to scan for.
14
+
15
+ For now, the serial can be found with:
16
+
17
+ $ lsusb -v |grep Serial
18
+
19
+ Once you find the device (if it has a serial, most phone's do.)
20
+
21
+ $ xscreen_usb_unlocker -s SERIAL
22
+
23
+ If it doesn't provide a serial, you can also use a device id. Either the vendor, device or both can be supplied in a `vendor:device` format.
24
+
25
+ To find these just run `lsusb` and look for the `1234:ABCD` piece, that is your device id.
26
+
27
+ $ xscreen_usb_unlocker -d 1234:ABCD
28
+
29
+ You can also save these so you don't have to retype them, or have them in your history:
30
+
31
+ $ xscreen_usb_unlocker -s SERIAL -d 1234:ABCD --save-config
32
+
33
+ Last, it can be daemonized, if this happens it will write a log to your homedirectory in: `~/.logs/xscreen_usb_unlocker.log`. After you've saved your config, this is the simplest way to enable/disable it.
34
+
35
+ $ xscreen_usb_unlocker -D
36
+
37
+ You can control logging and the logfile as well, likely I will disable the log for the 1.0 release.
38
+
39
+ If you unlock xscreensaver by hand, you are not disabling this, the next time you plug/unplug your usb device, it will lock again. This is useful if for some reason your usb device isn't available, you can still use your system as normal.
40
+
41
+ ## SECURITY
42
+
43
+ This is incredibly important to understand, their isn't any. This isn't a tool designed to secure your workstation, I can think of half a dozen ways to get past this easily.
44
+
45
+ Instead, this tool is for convience, to help facilitate simple locking/unlocking in a casual business/home environment.
46
+
47
+ All it does is start/kill (safely) xscreensaver based on scanning for usb devices.
48
+
49
+ ## TODO:
50
+
51
+ * Add a --kill option to remove the other daemonizes.
52
+ * Add a --name to trap for the device name (regex? substring?)
53
+ * Add basic device detection to print out a pretty table to help configure it (offer options and save automatically?)
54
+
55
+
56
+ ## Contributing
57
+
58
+ 1. Fork it
59
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
60
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
61
+ 4. Push to the branch (`git push origin my-new-feature`)
62
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/ruby
2
+ require 'xscreen_usb_unlocker'
3
+ include XscreenUsbUnlocker
4
+ Options.banner = "Usage: Used to lock/unlock xscreensaver based on usb device id and serial."
5
+ Options.on("-s", "--serial SERIAL", "make sure the device matches this serial.") { |s| Options[:serial] = s}
6
+ Options.on("-d", "--device DEVICE", "make sure it is this specific device.") { |d| Options[:device] = d}
7
+ Options.on("-D", "--daemonize", "Daemonize this process in the background.") { |d| Options[:daemonize] = true}
8
+ Options.on("--save-config", "Save the device and serial to a config file and exit.") { |d| Options[:save_config] = true}
9
+ Options.parse!
10
+
11
+ if !Options[:device] && !Options[:serial]
12
+ puts 'Must supply a device or serial to look for.'
13
+ exit
14
+ end
15
+
16
+ def lock_screen
17
+ Log.info 'locking'
18
+ p = spawn "xscreensaver -no-splash"
19
+ Process.detach p
20
+ %x[xscreensaver-command -lock]
21
+ end
22
+
23
+ def unlock_screen
24
+ Log.info 'unlocking'
25
+
26
+ if xscreensaver_pids
27
+ xscreensaver_pids.each do |p|
28
+ Log.info "Appears to be pid: #{p.pid}"
29
+ Process.kill "QUIT", p.pid
30
+ end
31
+ end
32
+ end
33
+
34
+ def xscreensaver_running?
35
+ xscreensaver_pids.any?
36
+ end
37
+
38
+ def xscreensaver_pids
39
+ Sys::ProcTable.ps.select{|x| x.cmdline.include?("xscreensaver") && !x.cmdline.include?("ruby")}
40
+ end
41
+
42
+ def plugged_in?
43
+ usb = LIBUSB::Context.new
44
+ options_hash = {}
45
+
46
+ if Options[:device]
47
+ v, p = Options[:device].split(":")
48
+ options_hash[:idVendor] = v.hex if v && !v.empty?
49
+ options_hash[:idProduct] = p.hex if p && !p.empty?
50
+ end
51
+
52
+ devices = usb.devices(options_hash)
53
+ return true if devices.select { |d| d.serial_number == Options[:serial]}.any?
54
+ false
55
+ end
56
+
57
+ def toggle_lock
58
+ if plugged_in?
59
+ Log.info 'unlock requested'
60
+ unlock_screen
61
+ else
62
+ Log.info 'lock request'
63
+ lock_screen
64
+ end
65
+ end
66
+
67
+ if __FILE__ == $0
68
+ # did we pry?
69
+ Options.on_pry
70
+
71
+ # should we save our config and bail?
72
+ if Options[:save_config]
73
+ Config["serial"] = Options[:serial] if Options[:serial]
74
+ Config["device"] = Options[:device] if Options[:device]
75
+ Config.save!
76
+ puts "Saved configuration to #{Config.file}"
77
+ exit
78
+ end
79
+
80
+ # kill a running copy of xscreensaver
81
+ if xscreensaver_running?
82
+ Log.info "xscreensaver appears to be running, killing so we can trap it."
83
+ %x[killall -QUIT xscreensaver]
84
+ end
85
+
86
+ # grab our notifications
87
+ Notifier = INotify::Notifier.new
88
+ Dir.glob("/dev/bus/usb/*").each do |d|
89
+ Notifier.watch(d, :delete, :create) do
90
+ toggle_lock
91
+ end
92
+ end
93
+
94
+ # fire off the lock cycle once.
95
+ toggle_lock
96
+
97
+ # start the notifier, which will fire off callbacks as needed.
98
+ if Options[:daemonize] || File.file?(Config.file)
99
+ Log.debug "Opening logfile."
100
+ Log.filename "/home/ebrodeur/.logs/xscreensaver_unlocker.log"
101
+ Log.debug "Daemonizing."
102
+ App.daemonize(:mulitple_pids => false) { Notifier.run }
103
+ else
104
+ Notifier.run
105
+ end
106
+ end
@@ -0,0 +1,75 @@
1
+ # Some requires, they don't fit elsewhere.
2
+ module XscreenUsbUnlocker
3
+
4
+ class Application
5
+ attr_accessor :version
6
+ attr_accessor :banner
7
+ attr_accessor :long_description
8
+ attr_accessor :plugins
9
+
10
+ # return the name of the app, for now this is just the cmd ran, later it will be
11
+ # something generated but more unique.
12
+ def name
13
+ $0.split("/").last
14
+ end
15
+
16
+ def cache_dir
17
+ "#{Dir.home}/.cache/erniebrodeur/#{App.name}/"
18
+ end
19
+
20
+ def config_dir
21
+ "#{Dir.home}/.config/erniebrodeur/#{App.name}/"
22
+ end
23
+
24
+ def pids
25
+ a = Sys::ProcTable.ps.select{|x| x.cmdline =~ /.*#{App.name}.*-[dD].*/}.map {|x| x.pid}
26
+ a.delete $$
27
+ return a if a.any?
28
+ nil
29
+ end
30
+
31
+ def initialize
32
+ @version = '0.0.0'
33
+ @banner = 'A bin snippet by Ernie Brodeur that does . . . something.'
34
+ @long_description = ''
35
+ @plugins = []
36
+ end
37
+
38
+ def daemonize(*params, &block)
39
+ if params[0] && !params[0][:multiple_pids] && pids
40
+ puts_or_log :info, "#{App.name} appears to be running (#{pids}), only one allowed, exiting."
41
+ exit
42
+ end
43
+ puts_or_log :info, "Forking to background."
44
+
45
+ Process.daemon
46
+ block.call
47
+ end
48
+
49
+ def kill_daemon
50
+ if !pids
51
+ puts_or_log :fatal, "No pids found, exiting."
52
+ end
53
+
54
+ pids.each do |p|
55
+ puts_or_log :info, "Killing #{p}"
56
+ `kill -TERM #{p}`
57
+ end
58
+ end
59
+ private
60
+ def puts_or_log(l, s)
61
+ if App.plugins.include? 'logging'
62
+ Log.send l, s
63
+ else
64
+ puts s
65
+ exit if l.to_sym == :fatal
66
+ end
67
+ end
68
+ end
69
+
70
+ App = Application.new
71
+
72
+ # This will load a helper, if it exists.
73
+ f = "#{$:.last}/helpers/#{App.name}.rb"
74
+ require f if File.exist? f
75
+ end
@@ -0,0 +1,40 @@
1
+ require 'fileutils'
2
+
3
+ module XscreenUsbUnlocker
4
+ class ConfigBlob < Hash
5
+ BaseDir = "/home/ebrodeur/.config/erniebrodeur"
6
+
7
+ def initialize
8
+ FileUtils.mkdir_p BaseDir if !Dir.exist? BaseDir
9
+ load
10
+ end
11
+
12
+ def file
13
+ "#{BaseDir}/#{App.name}.json"
14
+ end
15
+
16
+ def save
17
+ if any?
18
+ # I do this the long way because I want an immediate sync.
19
+ f = open(file, 'w')
20
+ f.write Yajl.dump self
21
+ f.sync
22
+ f.close
23
+ end
24
+ end
25
+
26
+ def save!
27
+ FileUtils.rm file if File.file? file
28
+ save
29
+ end
30
+
31
+ def load
32
+ if File.exist? self.file
33
+ h = Yajl.load open(file, 'r').read
34
+ h.each { |k,v| self[k.to_sym] = v}
35
+ end
36
+ end
37
+ end
38
+ App.plugins.push 'config'
39
+ Config = ConfigBlob.new
40
+ end
@@ -0,0 +1,96 @@
1
+ # it should override puts and print to 'capture' output as debug output.
2
+ # It should have a semi easy to read standard format
3
+ # it will have multiple formats available.
4
+ # it should produce colorized output (either parsed or part of the file format)
5
+ require 'logger'
6
+
7
+ module XscreenUsbUnlocker
8
+ class Logger
9
+ def initialize
10
+ @options = {}
11
+
12
+ @options[:utc] = true
13
+ @options[:level] = ::Logger::DEBUG
14
+ @options[:override_puts] = false
15
+ @options[:filename] = STDOUT
16
+
17
+ create_logger
18
+ end
19
+
20
+ def method_missing(sym, *args, &block)
21
+ @l.send sym, *args, &block
22
+ exit if sym == :fatal
23
+ end
24
+
25
+ def enable(sym)
26
+ raise 'NoSuchOption' if @options[sym] == nil
27
+ @options[sym] = true
28
+
29
+ if sym == :override_puts
30
+ level ::Logger::DEBUG
31
+ end
32
+ end
33
+
34
+ def disable(sym)
35
+ raise 'NoSuchOption' if @options[sym] == nil
36
+ @options[sym] = false
37
+ end
38
+
39
+ def level(s)
40
+ level = case s.to_sym
41
+ when :fatal then ::Logger::FATAL
42
+ when :error then ::Logger::ERROR
43
+ when :warn then ::Logger::WARN
44
+ when :info then ::Logger::INFO
45
+ when :debug then ::Logger::DEBUG
46
+ else ::Logger::UNKNOWN
47
+ end
48
+
49
+ @options[:level] = level
50
+ @l.level = level
51
+ end
52
+
53
+ def override_puts?
54
+ return true if @options[:override_puts]
55
+ false
56
+ end
57
+
58
+ def filename(file)
59
+ @options[:filename] = file
60
+ create_logger
61
+ end
62
+ private
63
+ def fmt_time
64
+ if @options[:utc]
65
+ Time.now.getutc
66
+ else
67
+ Time.now
68
+ end
69
+ end
70
+
71
+ def create_logger
72
+ FileUtils.mkdir_p File.dirname @options[:filename] if File.file? @options[:filename]
73
+ @l = ::Logger.new(@options[:filename])
74
+ @l.level = @options[:level]
75
+
76
+ @l.formatter = proc do |severity, datetime, progname, msg|
77
+ "[#{fmt_time.asctime}] [#{severity}]: #{msg}\n"
78
+ end
79
+
80
+ end
81
+ end
82
+
83
+ App.plugins.push 'logging'
84
+ Log = Logger.new
85
+ end
86
+
87
+ # better way to do this with: Kernel.module_eval def puts ...
88
+ module Kernel
89
+ def puts (s)
90
+ if Log.override_puts?
91
+ Log.info s
92
+ else
93
+ Kernel::puts s
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,59 @@
1
+ require 'optparse'
2
+
3
+ module XscreenUsbUnlocker
4
+ class OptionParser < ::OptionParser
5
+ def initialize
6
+ super
7
+ @options = {}
8
+ on("-V", "--version", "Print version") { |version| @options[:version] = true}
9
+ on("-p", "--pry", "open a pry shell.") { |pry| @options[:pry] = true}
10
+ if App.plugins.include? 'logging'
11
+ on("-l", "--log-level LEVEL", "Change the log level, default is debug.") { |level| Log.level level }
12
+ on("--log-file FILE", "What file to output to, default is STDOUT") { |file| Log.filename file }
13
+ end
14
+ end
15
+
16
+ # This will build an on/off option with a default value set to false.
17
+ def bool_on(word, description = "")
18
+ Options[word.to_sym] = false
19
+ on "-#{word.chars.first}", "--[no]#{word}", description do |o|
20
+ Options[word.to_sym] == o
21
+ end
22
+ end
23
+
24
+ def parse!
25
+ super
26
+
27
+ if @options[:version]
28
+ puts XscreenUsbUnlocker::Version
29
+ exit 0
30
+ end
31
+
32
+ # we need to mash in our config array. To do this we want to make config
33
+ # options that don't overwrite cli options.
34
+ if App.plugins.include? 'config'
35
+ Config.each do |k,v|
36
+ @options[k] = v if !@options[k]
37
+ end
38
+ end
39
+ end
40
+
41
+ def on_pry
42
+ if @options[:pry]
43
+ require 'pry'
44
+ binding.pry
45
+ end
46
+ end
47
+
48
+ def [](k)
49
+ @options[k]
50
+ end
51
+
52
+ def []=(k,v)
53
+ @options[k] = v
54
+ end
55
+ end
56
+
57
+ App.plugins.push "optparser"
58
+ Options = OptionParser.new
59
+ end
@@ -0,0 +1,3 @@
1
+ module XscreenUsbUnlocker
2
+ VERSION = "0.90.1"
3
+ end
@@ -0,0 +1,14 @@
1
+ require 'rb-inotify'
2
+ require 'libusb'
3
+ require 'yajl'
4
+ require 'sys/proctable'
5
+
6
+ require 'xscreen_usb_unlocker/app'
7
+ require 'xscreen_usb_unlocker/log'
8
+ require 'xscreen_usb_unlocker/config'
9
+ require 'xscreen_usb_unlocker/optparser'
10
+ require "xscreen_usb_unlocker/version"
11
+
12
+ module XscreenUsbUnlocker
13
+ # Your code goes here...
14
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/xscreen_usb_unlocker/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Ernie Brodeur"]
6
+ gem.email = ["ebrodeur@ujami.net"]
7
+ gem.description = "A CLI tool that scans your usb ports for a device, then locks/unlocks xscreensaver."
8
+ gem.summary = "This tool is used to control xscreensaver via a USB device being plugged in and removed. It provides minor methods to scan for unique device, like serial and device id. This is rather useful with a smart phone."
9
+ gem.homepage = "https://github.com/erniebrodeur/xscreen_usb_unlocker"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "xscreen_usb_unlocker"
15
+ gem.require_paths = ["lib"]
16
+ gem.add_runtime_dependency "rb-inotify"
17
+ gem.add_runtime_dependency "libusb"
18
+ gem.add_runtime_dependency "yajl-ruby"
19
+ gem.add_runtime_dependency "sys-proctable"
20
+ gem.add_development_dependency "pry"
21
+ gem.version = XscreenUsbUnlocker::VERSION
22
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xscreen_usb_unlocker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.90.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ernie Brodeur
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rb-inotify
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: libusb
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: yajl-ruby
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: sys-proctable
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: pry
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: A CLI tool that scans your usb ports for a device, then locks/unlocks
95
+ xscreensaver.
96
+ email:
97
+ - ebrodeur@ujami.net
98
+ executables:
99
+ - xscreen_usb_unlocker
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - .gitignore
104
+ - Gemfile
105
+ - LICENSE
106
+ - README.md
107
+ - Rakefile
108
+ - bin/xscreen_usb_unlocker
109
+ - lib/xscreen_usb_unlocker.rb
110
+ - lib/xscreen_usb_unlocker/app.rb
111
+ - lib/xscreen_usb_unlocker/config.rb
112
+ - lib/xscreen_usb_unlocker/log.rb
113
+ - lib/xscreen_usb_unlocker/optparser.rb
114
+ - lib/xscreen_usb_unlocker/version.rb
115
+ - xscreen_usb_unlocker.gemspec
116
+ homepage: https://github.com/erniebrodeur/xscreen_usb_unlocker
117
+ licenses: []
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ! '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 1.8.21
137
+ signing_key:
138
+ specification_version: 3
139
+ summary: This tool is used to control xscreensaver via a USB device being plugged
140
+ in and removed. It provides minor methods to scan for unique device, like serial
141
+ and device id. This is rather useful with a smart phone.
142
+ test_files: []
143
+ has_rdoc: