xpra icon
Bug tracker and wiki

Ticket #598: printing-v3.patch

File printing-v3.patch, 35.7 KB (added by Antoine Martin, 5 years ago)

updated patch with rpm packaging

  • src/etc/xpra/xpra.conf.in

     
    6060
    6161
    6262################################################################################
     63# File transfer
     64
     65# Receive files
     66file-transfer = yes
     67
     68# Location where files are received:
     69#download-path = ~/Downloads
     70#download-path = ~/Deskto
     71#download-path = /tmp
     72
     73# File size limit in MB
     74file-size-limit = 10
     75
     76# Print support:
     77printing = yes
     78
     79# How to open filee:
     80#open-command = xdg-open
     81
     82# Open files
     83open-files = no
     84
     85
     86################################################################################
    6387# Picture Encoding
    6488
    6589# Encodings allowed:
  • src/cups/xpraforwarder

     
     1#!/usr/bin/env python
     2# -*- coding: utf-8 -*-
     3#
     4# xpraforwarder - A CUPS backend written in Python. It uses GhostScript to create a
     5# PDF document and sends it via xpra to the client that requested the print.
     6# It is based on pdf2email by Georde Notaras.
     7#
     8# Copyright (c) George Notaras <George [D.O.T.] Notaras [A.T.] gmail [D.O.T.] com>
     9#
     10# License: GPLv2
     11#
     12# This program is released with absolutely no warranty, expressed or implied,
     13# and absolutely no support.
     14#
     15# This program is free software; you can redistribute it and/or modify
     16# it under the terms of the GNU General Public License as published by
     17# the Free Software Foundation; either version 2 of the License, or
     18# (at your option) any later version.
     19#
     20# This program is distributed in the hope that it will be useful,
     21# but WITHOUT ANY WARRANTY; without even the implied warranty of
     22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     23# GNU General Public License for more details.
     24#
     25# You should have received a copy of the GNU General Public License
     26# along with this program; if not, write to the:
     27#
     28# Free Software Foundation, Inc.,
     29# 59 Temple Place, Suite 330, Boston,
     30# MA 02111-1307  USA
     31#
     32
     33
     34import sys, os, syslog, datetime
     35import subprocess
     36
     37__version__ = "0.15.0"
     38
     39
     40def get_writable_dir():
     41        """Returns the writable directory path.
     42
     43        The Device URI of the printer is: {backend_name}:{/path/to/writable/dir}
     44        This is set in /etc/cups/printers.conf and is kept in an environmental
     45        variable named "DEVICE_URI" while this process runs.
     46        """
     47        dev_uri = os.environ['DEVICE_URI']
     48        write_dir = dev_uri.split(":")[1].strip()
     49        if os.path.exists(write_dir):
     50                if os.access(write_dir, os.R_OK | os.W_OK):
     51                        return write_dir
     52                else:
     53                        raise Exception("User does not have read/write access to: %s" % write_dir)
     54        else:
     55                raise Exception("Device URI: Path does not exist: %s" % write_dir)
     56
     57
     58def get_output_filename(request_datetime):
     59        """Returns a formatted filename.
     60
     61        Filename format: {user}_{job-id}_{date_time}.pdf"""
     62        return "%s_%s_%s.pdf" % (sys.argv[2], sys.argv[1], request_datetime.strftime("%Y%m%d_%H%M%S"))
     63
     64
     65def get_output_filepath(request_datetime):
     66        """Returns the full path to the output pdf file."""
     67        return os.path.join(get_writable_dir(), get_output_filename(request_datetime))
     68
     69def exec_command(command):
     70        #print("exec_command(%s)" % command)
     71        proc = subprocess.Popen(command, shell=True)
     72        return proc.wait()
     73
     74def create_pdf(output_file):
     75        """Creates the PDF file.
     76
     77        Runs the GS interpreter as a child process and returns the exit code.
     78
     79        External Tip: when creating pdf files with GS, an initial 'save' helps so
     80                                  that fonts are not flushed between pages. (tip source unknown)
     81        -sPAPERSIZE acts like a default value and needs to be set, but does not affect the final print.
     82        """
     83        command = "gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -sPAPERSIZE=a4 -dPDFSETTINGS=/printer -sOutputFile=%s -c save pop -f - &> /dev/null" % output_file
     84        return exec_command(command)
     85
     86def xpra_print(output_file):
     87        #the easy way: spawn yet another process:
     88        #the right way: open the socket, etc..
     89        command = "xpra control '%s' print '%s' default UI 1000000 ''" % (os.environ.get("DISPLAY", ""), output_file)
     90        return exec_command(command)
     91
     92
     93def logger(msg):
     94        """Writes a syslog entry (msg) at the default facility and on level ERROR."""
     95        syslog.syslog(syslog.LOG_ERR, "CUPS PDF backend: %s" % msg)
     96
     97
     98def main():
     99        if len(sys.argv) == 1:
     100                # Without arguments should give backend info.
     101                # This is also used when lpinfo -v is issued, where it should include "direct this_backend"
     102                sys.stdout.write("direct %s \"Unknown\" \"Direct PDF Printing/Forwarding to host via xpra\"\n" % os.path.basename(sys.argv[0]))
     103                sys.stdout.flush()
     104                sys.exit(0)
     105        if len(sys.argv) not in (5,6):
     106                sys.stdout.write("Usage: %s job-id user title copies options [file]\n" % os.path.basename(sys.argv[0]))
     107                sys.stdout.flush()
     108                logger("Wrong number of arguments. Usage: %s job-id user title copies options [file]" % sys.argv[0])
     109                sys.exit(1)
     110        request_datetime = datetime.datetime.now()
     111        try:
     112                output_file = get_output_filepath(request_datetime)
     113                #logger("output file=%s" % output_file)
     114        except Exception, err:
     115                #logger("xpraforwarder error: %s" % err)
     116                if err[0] <> "DEVICE_URI":      # (TODO) RE-EXAMINE THIS - This occurs when CUPS is (re)started. Probably an internal backend check.
     117                        logger(err[0])
     118        else:
     119                ret_code = create_pdf(output_file)
     120                pdferror = ret_code!=0
     121                if pdferror:
     122                        logger("Failed to create PDF document (exit code: %s)" % ret_code)
     123                        return
     124                try:
     125                        xpra_print(output_file)
     126                except:
     127                        logger("Failed to forward the PDF document to user: %s" % sys.argv[2])
     128                if os.path.exists(output_file):
     129                        os.remove(output_file)
     130
     131
     132if __name__=='__main__':
     133        main()
     134
  • src/setup.py

    Property changes on: src/cups/xpraforwarder
    ___________________________________________________________________
    Added: svn:executable
    ## -0,0 +1 ##
    +*
    \ No newline at end of property
     
    118118if WIN32 or OSX:
    119119    Xdummy_ENABLED = False
    120120sound_ENABLED           = True
     121printing_ENABLED        = not WIN32
    121122
    122123enc_proxy_ENABLED       = True
    123124enc_x264_ENABLED        = True          #too important to detect
     
    165166            "clipboard",
    166167            "server", "client", "x11", "gtk_x11",
    167168            "gtk2", "gtk3", "html5",
    168             "sound", "opengl",
     169            "sound", "opengl", "printing",
    169170            "rebuild",
    170171            "warn", "strict", "shadow", "debug", "PIC",
    171172            "Xdummy", "Xdummy_wrapper", "verbose", "tests", "bundle_tests"]
     
    16001601        add_data_files(html5_dir+k, v)
    16011602
    16021603
     1604if printing_ENABLED:
     1605    #"/usr/lib/cups/backend":
     1606    cups_backend_dir = os.path.join(sys.prefix, "lib", "cups", "backend")
     1607    add_data_files(cups_backend_dir, ["cups/xpraforwarder"])
    16031608
     1609
     1610
    16041611#*******************************************************************************
    16051612#which file to link against (new-style buffers or old?):
    16061613if memoryview_ENABLED:
  • src/xpra/client/client_base.py

     
    1515
    1616from xpra.log import Logger
    1717log = Logger("client")
     18printlog = Logger("printing")
     19filelog = Logger("file")
    1820
    1921from xpra.net.protocol import Protocol, get_network_caps, sanity_checks
    2022from xpra.scripts.config import ENCRYPTION_CIPHERS
     
    7678        self.min_quality = 0
    7779        self.speed = 0
    7880        self.min_speed = -1
     81        self.file_transfer = False
     82        self.printing = False
     83        self.open_command = None
    7984        #protocol stuff:
    8085        self._protocol = None
    8186        self._priority_packets = []
     
    108113        self.min_quality = opts.min_quality
    109114        self.speed = opts.speed
    110115        self.min_speed = opts.min_speed
     116        self.file_transfer = opts.file_transfer
     117        self.printing = opts.printing
     118        self.open_command = opts.open_command
    111119
    112120        if DETECT_LEAKS:
    113121            from xpra.util import detect_leaks
     
    184192
    185193    def init_packet_handlers(self):
    186194        self._packet_handlers = {
    187             "hello": self._process_hello,
     195            "hello"             : self._process_hello,
     196            "query-printers"    : self._process_query_printers,
     197            "send-file"         : self._process_send_file,
    188198            }
    189199        self._ui_packet_handlers = {
    190200            "challenge":                self._process_challenge,
     
    242252                "version"               : local_version,
    243253                "encoding.generic"      : True,
    244254                "namespace"             : True,
     255                "file-transfer"         : self.file_transfer,
     256                "printing"              : self.printing,
    245257                "hostname"              : socket.gethostname(),
    246258                "uuid"                  : self.uuid,
    247259                "username"              : self.username,
     
    562574        #legacy, should not be used for anything
    563575        pass
    564576
     577
     578    def _process_query_printers(self, packet):
     579        from xpra.platform.printing import get_printers
     580        printers = get_printers()
     581        printlog("process_query_printers(%s) found printers=%s", packet, printers)
     582        self.send("printers", printers)
     583
     584
     585    def _process_send_file(self, packet):
     586        #send-file basefilename, printit, openit, filesize, 0, data)
     587        assert self.file_transfer
     588        from xpra.platform.features import DOWNLOAD_PATH
     589        basefilename, printit, openit, filesize, data, maxbitrate, printer, title, options = packet[1:10]
     590        assert maxbitrate>=0
     591        assert filesize>0 and data
     592        filename = os.path.abspath(os.path.join(os.path.expanduser(DOWNLOAD_PATH), os.path.basename(basefilename)))
     593        if os.path.exists(filename):
     594            filelog.error("cannot save file %s: file already exists", filename)
     595            return
     596        #TODO: maybe use mkstemp instead of EXCL?
     597        fd = os.open(filename, os.O_CREAT | os.O_RDWR | os.O_EXCL)
     598        f = os.fdopen(fd,'wb')
     599        try:
     600            f.write(data)
     601        finally:
     602            f.close()
     603        filelog.info("downloaded %s bytes to %s", filesize, filename)
     604        if printit:
     605            assert self.printing
     606            self._print_file(filename, printer, title, options)
     607            return
     608        elif openit:
     609            #run the command in a new thread
     610            #so we can block waiting for the subprocess to exit
     611            #(ensures that we do reap the process)
     612            import thread
     613            thread.start_new_thread(self._open_file, (filename, ))
     614
     615    def _print_file(self, filename, printer, title, options):
     616        from xpra.platform.printing import print_files
     617        job = print_files(printer, [filename], title, options)
     618        printlog("printing %s, job=%s", filename, job)
     619
     620    def _open_file(self, filename):
     621        import subprocess
     622        PIPE = subprocess.PIPE
     623        process = subprocess.Popen([self.open_command, filename], stdin=PIPE, stdout=PIPE, stderr=PIPE)
     624        out, err = process.communicate()
     625        r = process.wait()
     626        filelog.info("opened file %s with %s, exit code: %s", filename, self.open_command, r)
     627        if r!=0:
     628            l = filelog.warn
     629        else:
     630            l = filelog
     631        if out:
     632            l("stdout=%s", nonl(out)[:512])
     633        if err:
     634            l("stderr=%s", nonl(err)[:512])
     635
     636
    565637    def _process_gibberish(self, packet):
    566638        (_, message, data) = packet
    567639        p = self._protocol
  • src/xpra/log.py

     
    115115                 "window", "icon", "info", "launcher", "mdns", "cursor",
    116116                 "mmap", "network", "protocol", "crypto", "encoder", "stats",
    117117                 "notify", "xsettings", "grab", "xshm", "workspace",
    118                  "sound", "events",
     118                 "sound", "printing", "file", "events",
    119119                 "opengl",
    120120                 "osx", "win32",
    121121                 "paint", "platform", "import",
  • src/xpra/platform/darwin/printing.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6# darwin: assume cups support (no overrides needed here)
  • src/xpra/platform/features.py

     
    44# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    55# later version. See the file COPYING for details.
    66
     7import os.path
     8
    79#defaults which may be overriden by platform_import:
    810LOCAL_SERVERS_SUPPORTED = False
    911SHADOW_SUPPORTED = False
     
    1719CLIPBOARD_NATIVE_CLASS = None
    1820CAN_DAEMONIZE = False
    1921UI_THREAD_POLLING = 0
     22OPEN_COMMAND = "xdg-open"
     23DOWNLOAD_PATH = "~/Downloads"
     24if not os.path.exists(os.path.expanduser(DOWNLOAD_PATH)):
     25    DOWNLOAD_PATH = "~"
    2026
    2127DEFAULT_PULSEAUDIO_COMMAND = "pulseaudio --start --daemonize=false --system=false " + \
    2228                                    "--exit-idle-time=-1 -n --load=module-suspend-on-idle " + \
     
    3440                "DEFAULT_PULSEAUDIO_COMMAND",
    3541                "DEFAULT_XVFB_COMMAND",
    3642                "GOT_PASSWORD_PROMPT_SUGGESTION",
     43                "DOWNLOAD_PATH",
    3744                "CLIPBOARDS",
    3845                "CLIPBOARD_WANT_TARGETS",
    3946                "CLIPBOARD_GREEDY",
  • src/xpra/platform/printing.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6#default implementation uses pycups
     7from xpra.log import Logger
     8log = Logger("printing")
     9
     10def err(*args):
     11    log.error(*args)
     12
     13def get_printers():
     14    return {}
     15
     16def print_files(printer, filenames, title, options):
     17    raise Exception("no print implementation available")
     18
     19def printing_finished(printpid):
     20    return True
     21   
     22
     23#default implementation uses pycups:
     24from xpra.platform import platform_import
     25try:
     26    from xpra.platform.pycups_printing import get_printers, print_files, printing_finished
     27    assert get_printers and print_files and printing_finished
     28except Exception as e:
     29    err("cannot use pycups for printing: %s", e)
     30
     31platform_import(globals(), "printing", False,
     32                "get_printers",
     33                "print_files",
     34                "printing_finished")
     35
     36
     37def main():
     38    import sys
     39    if "-v" in sys.argv or "--verbose" in sys.argv:
     40        from xpra.log import add_debug_category
     41        add_debug_category("util")
     42
     43    from xpra.util import nonl, pver
     44    def print_dict(d):
     45        for k in sorted(d.keys()):
     46            v = d[k]
     47            print("* %s : %s" % (k.ljust(32), nonl(pver(v))))
     48    from xpra.platform import init, clean
     49    try:
     50        init("Printing", "Printing")
     51        print_dict(get_printers())
     52    finally:
     53        clean()
     54
     55if __name__ == "__main__":
     56    main()
  • src/xpra/platform/pycups_printing.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6#default implementation using pycups
     7import cups
     8
     9from xpra.log import Logger
     10log = Logger("printing")
     11
     12
     13def get_printers():
     14    conn = cups.Connection()
     15    printers = conn.getPrinters()
     16    log("getPrinters()=%s", printers)
     17    return printers
     18
     19def print_files(printer, filenames, title, options):
     20    conn = cups.Connection()
     21    printpid = conn.printFiles(printer, filenames, title, options)
     22    log("printFiles%s=%s", (printer, filenames, title, options), printpid)
     23    return printpid
     24
     25def printing_finished(printpid):
     26    conn = cups.Connection()
     27    f = conn.getJobs().get(printpid, None) is None
     28    log("printingFinished(%s)=%s", printpid, f)
     29    return f
  • src/xpra/platform/win32/features.py

     
    77# Platform-specific code for Win32.
    88import os
    99
     10def get_registry_value(key, reg_path, entry):
     11    import win32api             #@UnresolvedImport
     12    hKey = win32api.RegOpenKey(key, reg_path)
     13    value, _ = win32api.RegQueryValueEx(hKey, entry)
     14    win32api.RegCloseKey(hKey)
     15    return    value
     16
     17
    1018SYSTEM_TRAY_SUPPORTED = True
    1119SHADOW_SUPPORTED = True
    1220os.environ["PLINK_PROTOCOL"] = "ssh"
    1321DEFAULT_SSH_CMD = "plink"
     22PRINT_COMMAND = ""
     23#TODO: use "FOLDERID_Downloads":
     24# FOLDERID_Downloads = "{374DE290-123F-4565-9164-39C4925E467B}"
     25# maybe like here:
     26# https://gist.github.com/mkropat/7550097
     27#from win32com.shell import shell, shellcon
     28#shell.SHGetFolderPath(0, shellcon.CSIDL_MYDOCUMENTS, None, 0)
     29try:
     30    #use the internet explorer registry key:
     31    #HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer
     32    import win32con             #@UnresolvedImport
     33    DOWNLOAD_PATH = get_registry_value(win32con.HKEY_CURRENT_USER, "Software\\Microsoft\\Internet Explorer", "Download Directory")
     34except:
     35    #fallback to what the documentation says is the default:
     36    DOWNLOAD_PATH = os.path.join(os.environ.get("USERPROFILE", "~"), "Downloads")
    1437GOT_PASSWORD_PROMPT_SUGGESTION = \
    1538   'Perhaps you need to set up Pageant, or (less secure) use --ssh="plink -pw YOUR-PASSWORD"?\n'
    1639CLIPBOARDS=["CLIPBOARD"]
  • src/xpra/platform/win32/printing.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6from xpra.log import Logger
     7log = Logger("printing")
     8
     9import win32print       #@UnresolvedImport
     10import win32api         #@UnresolvedImport
     11
     12
     13def get_printers():
     14    printers = {}
     15    #default_printer = win32print.GetDefaultPrinter()
     16    for p in win32print.EnumPrinters(win32print.PRINTER_ENUM_LOCAL, None, 1):
     17        flags, desc, name, comment = p
     18        log("found printer: %s, %s, %s, %s", flags, desc, name, comment)
     19        #phandle = win32print.OpenPrinter(name)
     20        #win32print.ClosePrinter(phandle)
     21        printers[name] = (desc, comment)
     22    log("printers=%s", printers)
     23    return printers
     24
     25def print_files(printer, filenames, title, options):
     26    #AcroRd32.exe /N /T PdfFile PrinterName [ PrinterDriver [ PrinterPort ] ]
     27    for f in filenames:
     28        #win32api.ShellExecute(0, "print", f, '/d:"%s"' % printer, ".", 0)
     29        win32api.ShellExecute(0, "printto", f, '"%s"' % printer, ".", 0)
  • src/xpra/platform/xposix/printing.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6# posix: assume cups support (no overrides needed here)
  • src/xpra/scripts/config.py

     
    265265                    "window-layout"     : str,
    266266                    "display"           : str,
    267267                    "tcp-proxy"         : str,
     268                    "download-path"     : str,
     269                    "open-command"      : str,
    268270                    "debug"             : str,
    269271                    "input-method"      : str,
    270272                    "microphone"        : str,
     
    279281                    "compression_level" : int,
    280282                    "dpi"               : int,
    281283                    "scaling"           : int,
     284                    "file-size-limit"   : int,
    282285                    #float options:
    283286                    "auto-refresh-delay": float,
    284287                    #boolean options:
     
    308311                    "exit-ssh"          : bool,
    309312                    "opengl"            : bool,
    310313                    "mdns"              : bool,
     314                    "file-transfer"     : bool,
     315                    "printing"          : bool,
     316                    "open-files"        : bool,
    311317                    "swap-keys"         : bool,
    312318                    "start-new-commands": bool,
    313319                    "remote-logging"    : bool,
     
    334340    global GLOBAL_DEFAULTS
    335341    if GLOBAL_DEFAULTS is not None:
    336342        return GLOBAL_DEFAULTS
    337     from xpra.platform.features import DEFAULT_SSH_CMD, DEFAULT_PULSEAUDIO_COMMAND, DEFAULT_XVFB_COMMAND
     343    from xpra.platform.features import DEFAULT_SSH_CMD, DOWNLOAD_PATH, OPEN_COMMAND, DEFAULT_PULSEAUDIO_COMMAND, DEFAULT_XVFB_COMMAND
    338344    try:
    339345        from xpra.platform.info import get_username
    340346        username = get_username()
     
    365371                    "window-layout"     : "",
    366372                    "display"           : "",
    367373                    "tcp-proxy"         : "",
     374                    "download-path"     : DOWNLOAD_PATH,
     375                    "open-command"      : OPEN_COMMAND,
    368376                    "debug"             : "",
    369377                    "input-method"      : "none",
    370378                    "sound-source"      : "",
     
    377385                    "compression_level" : 1,
    378386                    "dpi"               : 0,
    379387                    "scaling"           : 1,
     388                    "file-size-limit"   : 10,
    380389                    "auto-refresh-delay": 0.25,
    381390                    "daemon"            : True,
    382391                    "use-display"       : False,
     
    408417                    "exit-ssh"          : True,
    409418                    "opengl"            : OPENGL_DEFAULT,
    410419                    "mdns"              : False,
     420                    "file-transfer"     : True,
     421                    "printing"          : True,
     422                    "open-files"        : False,
    411423                    "swap-keys"         : sys.platform.startswith("darwin"),    #only used on osx
    412424                    "encodings"         : ["all"],
    413425                    "video-encoders"    : ["all"],
  • src/xpra/scripts/main.py

     
    663663
    664664    options, args = parser.parse_args(cmdline[1:])
    665665
     666    #FIXME: file tranfer command line options:
     667    hidden_options["file_transfer"] = defaults.file_transfer
     668    hidden_options["file_size_limit"] = defaults.file_size_limit
     669    hidden_options["printing"] = defaults.printing
     670    hidden_options["open_command"] = defaults.open_command
     671    hidden_options["open_files"] = defaults.open_files
     672
    666673    #ensure all the option fields are set even though
    667674    #some options are not shown to the user:
    668675    for k,v in hidden_options.items():
  • src/xpra/server/server_base.py

     
    9797        self.supports_clipboard = False
    9898        self.supports_dbus_proxy = False
    9999        self.dbus_helper = None
     100        self.file_transfer = False
     101        self.printing = False
    100102        self.exit_with_children = False
    101103        self.start_new_commands = False
    102104        self.remote_logging = False
     
    128130                    "scaling", "scaling-control",
    129131                    "suspend", "resume", "name", "ungrab",
    130132                    "key", "focus", "workspace",
    131                     "client", "start", "start-child"]
     133                    "client", "start", "start-child",
     134                    "send-file", "print"]
    132135
    133136        self.init_encodings()
    134137        self.init_packet_handlers()
     
    184187        self.remote_logging = opts.remote_logging
    185188        self.env = parse_env(opts.env)
    186189        self.send_pings = opts.pings
     190        self.file_transfer = opts.file_transfer
     191        self.printing = opts.printing
    187192        self.notifications_forwarder = None
    188193        self.notifications = opts.notifications
    189194        self.scaling_control = parse_bool_or_int("scaling", opts.scaling)
     
    432437            "set-bell":                             self._process_set_bell,
    433438            "logging":                              self._process_logging,
    434439            "command_request":                      self._process_command_request,
     440            "printers":                             self._process_printers,
    435441                                          }
    436442        self._authenticated_ui_packet_handlers = self._default_packet_handlers.copy()
    437443        self._authenticated_ui_packet_handlers.update({
     
    796802            # now we can set the modifiers to match the client
    797803            self.send_windows_and_cursors(ss, share_count>0)
    798804
     805        if self.printing and ss.printing:
     806            ss.query_printers()
     807
    799808        ss.startup_complete()
    800809        self.server_event("startup-complete", ss.uuid)
    801810
     
    861870                 "bell"                         : self.bell,
    862871                 "cursors"                      : self.cursors,
    863872                 "dbus_proxy"                   : self.supports_dbus_proxy,
     873                 "file-transfer"                : self.file_transfer,
     874                 "printing"                     : self.printing,
    864875                 "start-new-commands"           : self.start_new_commands,
    865876                 "exit-with-children"           : self.exit_with_children,
    866877                 })
     
    909920        for x in msg.splitlines():
    910921            clientlog.log(level, x)
    911922
     923    def _process_printers(self, proto, packet):
     924        ss = self._server_sources.get(proto)
     925        if ss is None:
     926            return
     927        printers = packet[1]
     928        ss.set_printers(printers)
     929
     930
    912931    def _process_command_request(self, proto, packet):
    913932        """ client sent a command request through its normal channel """
    914933        assert len(packet)>=2, "invalid command request packet (too small!)"
     
    966985                    raise Exception("invalid window id: %s" % x)
    967986            return wids
    968987
     988        def get_sources(client_uuids_str, attr=None):
     989            #find the client uuid specified:
     990            if client_uuids_str=="UI":
     991                sources = [ss for ss in self._server_sources.values() if ss.ui_client]
     992                client_uuids = [ss.uuid for ss in sources]
     993                notfound = []
     994            elif client_uuids_str=="*":
     995                sources = self._server_sources.values()
     996                client_uuids = [ss.uuid for ss in sources]
     997            else:
     998                client_uuids = client_uuids_str.split(",")
     999                sources = [ss for ss in self._server_sources.values() if ss.uuid in client_uuids]
     1000                notfound = [x for x in client_uuids if x not in [ss.uuid for ss in sources]]
     1001                if notfound:
     1002                    commandlog.warn("client connection not found for uuid(s): %s", notfound)
     1003            if attr:
     1004                attrerr = [x for x in client_uuids if (x not in notfound and not getattr(ss, attr))]
     1005                if attrerr:
     1006                    commandlog.warn("client connections do not support %s: %s", attr, attrerr)
     1007            return sources
     1008
    9691009        #handle commands that either don't require a client,
    9701010        #or can work on more than one connected client:
    9711011        if command in ("help", "hello"):
     
    12001240            if not proc:
    12011241                return 1, "failed to start new child command %s" % str(cmd)
    12021242            return 0, "new child started"
     1243        elif command=="send-file":
     1244            #ie: send-file filename open|noopen client_uuids maxbitrate
     1245            if len(args)!=4:
     1246                return argn_err(4)
     1247            filename = os.path.abspath(os.path.expanduser(args[0]))
     1248            if not os.path.exists(filename):
     1249                return arg_err("file '%s' does not exist" % filename)
     1250            openit = args[1] in ("open", "true", "1")
     1251            #find the client uuid specified:
     1252            client_uuids = args[2]
     1253            sources = get_sources(client_uuids, "file_transfer")
     1254            if not sources:
     1255                return arg_err("no clients found matching: %s" % client_uuids)
     1256            maxbitrate = args[3]
     1257            for ss in sources:
     1258                assert ss.file_transfer
     1259                ss.send_file(filename, False, openit, ss, maxbitrate)
     1260            return 0, "file transfer to %s initiated" % client_uuids
     1261        elif command=="print":
     1262            #ie: print filename printer client_uuids maxbitrate title *options
     1263            if len(args)<4:
     1264                return argn_err("at least 4")
     1265            filename = os.path.abspath(os.path.expanduser(args[0]))
     1266            if not os.path.exists(filename):
     1267                return arg_err("file '%s' does not exist" % filename)
     1268            printer = args[1]
     1269            #find the client uuid specified:
     1270            client_uuids = args[2]
     1271            sources = get_sources(client_uuids, "printing")
     1272            if not sources:
     1273                return arg_err("no clients found matching: %s" % client_uuids)
     1274            maxbitrate = args[3]
     1275            title = ""
     1276            options = {}
     1277            if len(args)>=5:
     1278                title = args[4]
     1279            if len(args)>=6:
     1280                for arg in args[5:]:
     1281                    argp = arg.split("=", 1)
     1282                    if len(argp)==2 and len(argp[0])>0:
     1283                        options[argp[0]] = argp[1]
     1284            for ss in sources:
     1285                ss = sources[0]
     1286                assert ss.printing
     1287                ss.send_file(filename, True, True, ss, maxbitrate, printer, title, options)
     1288            return 0, "printing to %s initiated" % client_uuids
    12031289        else:
    12041290            return ServerCore.do_handle_command_request(self, command, args)
    12051291
  • src/xpra/server/source.py

     
    1919keylog = Logger("keyboard")
    2020cursorlog = Logger("cursor")
    2121metalog = Logger("metadata")
     22printlog = Logger("printing")
    2223
     24
    2325from xpra.server import ClientException
    2426from xpra.server.source_stats import GlobalPerformanceStatistics
    2527from xpra.server.window_video_source import WindowVideoSource
     
    3234from xpra.net import compression
    3335from xpra.net.compression import compressed_wrapper, Compressed, Uncompressed
    3436from xpra.daemon_thread import make_daemon_thread
    35 from xpra.os_util import platform_name, thread, Queue, get_machine_id, get_user_uuid
     37from xpra.os_util import platform_name, thread, Queue, get_machine_id, get_user_uuid, load_binary_file
    3638from xpra.server.background_worker import add_work_item
    3739from xpra.util import std, typedict, updict, get_screen_info, CLIENT_PING_TIMEOUT, WORKSPACE_UNSET, DEFAULT_METADATA_SUPPORTED
    3840
     
    287289        self.metadata_supported = []
    288290        self.show_desktop_allowed = False
    289291        self.supports_transparency = False
     292        self.file_transfer = False
     293        self.printing = False
     294        self.printers = {}
    290295        self.vrefresh = -1
    291296        self.double_click_time  = -1
    292297        self.double_click_distance = -1, -1
     
    501506        self.clipboard_notifications = c.boolget("clipboard.notifications")
    502507        self.clipboard_set_enabled = c.boolget("clipboard.set_enabled")
    503508        self.share = c.boolget("share")
     509        self.file_transfer = c.boolget("file-transfer")
     510        self.printing = c.boolget("printing")
    504511        self.named_cursors = c.boolget("named_cursors")
    505512        self.window_initiate_moveresize = c.boolget("window.initiate-moveresize")
    506513        self.system_tray = c.boolget("system_tray")
     
    674681                    self.video_helper.add_encoder_spec(encoding, colorspace, spec)
    675682
    676683
     684    def query_printers(self):
     685        assert self.printing
     686        self.send("query-printers")
     687
     688
    677689    def startup_complete(self):
    678690        log("startup_complete()")
    679691        if self.notify_startup_complete:
     
    10811093            info[k] = bool(getattr(self, prop))
    10821094        for prop in ("named_cursors", "share", "randr_notify",
    10831095                     "clipboard_notifications", "system_tray",
    1084                      "lz4", "lzo"):
     1096                     "lz4", "lzo",
     1097                     "printing", "file_transfer"):
    10851098            battr(prop, prop)
    10861099        for prop, name in {"clipboard_enabled"  : "clipboard",
    10871100                           "send_windows"       : "windows",
     
    10931106                           "double_click_time"      : "double_click.time",
    10941107                           "double_click_distance"  : "double_click.distance"}.items():
    10951108            info[name] = getattr(self, prop)
     1109        if self.printers:
     1110            info("printers", self.printers.keys())
    10961111        return info
    10971112
    10981113    def get_sound_info(self):
     
    12971312        self.send("set_deflate", level)
    12981313
    12991314
     1315    def set_printers(self, printers):
     1316        printlog("set_printers(%s)", printers)
     1317        self.printers = printers
     1318
     1319    def send_file(self, filename, printit, openit, ss, maxbitrate=0, printer="", title="", options={}):
     1320        log.info("send_file%s", (filename, printit, openit, ss, maxbitrate, printer, title, options))
     1321        #TODO:
     1322        # * client ACK
     1323        # * stream it (don't load the whole file!)
     1324        # * timeouts
     1325        # * checksum
     1326        # * rate control
     1327        # * xpra info with queue and progress
     1328        basefilename = os.path.basename(filename)
     1329        filesize = os.path.getsize(filename)
     1330        data = load_binary_file(filename)
     1331        assert filesize>0 and data
     1332        cdata = compressed_wrapper("file-data", data, level=5, lz4=self.lz4)
     1333        self.send("send-file", basefilename, printit, openit, filesize, cdata, maxbitrate, printer, title, options)
     1334
    13001335    def send_client_command(self, *args):
    13011336        self.send("control", *args)
    13021337
  • rpmbuild/xpra.spec

     
    232232%{_datadir}/applications/xpra_launcher.desktop
    233233%{_datadir}/applications/xpra.desktop
    234234%{_datadir}/icons/xpra.png
     235/usr/lib/cups/backend/xpraforwarder
    235236%dir %{_sysconfdir}/xpra
    236237%config(noreplace) %{_sysconfdir}/xpra/xorg.conf
    237238%config(noreplace) %{_sysconfdir}/xpra/xpra.conf
     
    267268%post
    268269/usr/bin/update-desktop-database &> /dev/null || :
    269270/bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null || :
     271/bin/chmod 700 /usr/lib/cups/backend/xpraforwarder
    270272
    271273
    272274%postun