#!/usr/bin/python
'''
printermsg.py -- Display messages on networked HP printers
Run "printermsg.py --help" to see usage information. This script
has only been tested under Python 2.5 (but will likely work on 2.4).
Please don't abuse this. Eric Hayes might get annoyed.
'''
# Copyright (c) 2008 Thomas W. Most
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
import sys
import time
import random
import socket
import textwrap
import optparse
# Just guessing at what's allowed here
SAFE_CHARS = set(
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"1234567890!?-'\\/_,.: "
)
# Takes a single % argument
COMMAND = ('\x1B%%-12345X@PJL RDYMSG DISPLAY = '
'"%s"\r\n\x1B%%-12345X\r\n')
DEFAULT_PORT = 9100
def send_message(sock, message, wrap=0, take_last=0):
'''
Send the message to the socket, optionally wrapping
to `wrap` characters (0 means no wrap). Use
`take_last` to display only the last n wrapped lines,
or specify ``0`` to have the text overflow if it is
too long.
'''
if wrap:
lines = textwrap.wrap(message, wrap)
if take_last:
lines = lines[-take_last:]
message = ''.join(l.lstrip().ljust(wrap) for l in lines)
try:
sock.send(COMMAND % message)
except socket.error, err:
sys.stderr.write("Error sending message on %s: %s" % (sock, err))
def scroll_message(sockets, message, cols):
'''
Scroll the `message` across the displays of the
printer at each socket in `sockets`. `cols`
is the length of a single line on each printer's
display.
'''
message = message.ljust(cols) + (" " * cols)
try:
while True:
m = message[:cols]
for socket in sockets:
send_message(socket, m)
message = message[1:] + message[0]
print message
time.sleep(.2)
except KeyboardInterrupt:
for s in sockets: send_message(s, "")
print "Animation terminated."
def type_message(sockets, message, rows, cols):
'''
Types out the `message` on each of `sockets`.
'''
chars = list(message)
current = []
while chars:
current.append(chars.pop(0))
m = ''.join(current)
for s in sockets:
send_message(s, m, cols, rows)
print m
time.sleep(.2)
def main(arguments=sys.argv[1:]):
usage = "usage: %prog [options] printer-host[:port] ..."
parser = optparse.OptionParser(usage=usage, version="%prog 1.0")
parser.add_option("-m", "--message", dest="messages",
metavar="MESSAGE", action="append",
help="A message to display. If more than one is specified, "
"one will be chosen randomly from among those provided.")
parser.add_option("-d", "--display",
default="wrap",
help="How to display: static, wrap, typewriter or scroll. "
"(Specifying keyboard or scroll will result in a long-"
"running process.) [default: %default]")
parser.add_option("-p", "--postfix",
default="",
help="Text to append to all printer-host hostnames")
parser.add_option("-c", "--cols",
default="16",
help="The character length of the printer's display.")
parser.add_option("-r", "--rows",
default="2",
help="The number of character rows on the printer's display.")
options, args = parser.parse_args(arguments)
messages = []
for m in (options.messages or ()):
messages.append(''.join(c for c in m if c in SAFE_CHARS))
if not messages:
messages.append("TOUCH ME")
try:
cols = int(options.cols)
if cols <= 0:
raise ValueError
except ValueError:
parser.error("%s is not valid for --cols")
try:
rows = int(options.rows)
if rows <= 0:
raise ValueError
except ValueError:
parser.error("%s is not valid for --rows")
if len(args) < 1:
parser.error("you must specify at least one printer-host")
sockets = []
for host in args:
if ":" in host:
host, port = host.rsplit(':', 1)
try:
port = int(port)
except ValueError:
parser.error("%s is an invalid port number" % port)
else:
port = DEFAULT_PORT
if options.postfix:
host = host + options.postfix
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
except socket.error:
sys.stderr.write("Unable to connect to %s:%s\n" % (
host, port))
else:
sockets.append(s)
if options.display in ("static", "wrap"):
for s in sockets:
send_message(s, random.choice(messages),
cols if options.display == "static" else 0)
elif options.display == "typewriter":
type_message(sockets, random.choice(messages), rows, cols)
elif options.display == "scroll":
scroll_message(sockets, random.choice(messages), cols)
else:
parser.error("%s is not a valid display value" %
options.display)
if __name__ == '__main__':
main()