179 lines
5.4 KiB
Python
Executable File
179 lines
5.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# examples/get_selection.py -- demonstrate getting selections
|
|
# (equivalent to pasting from the clipboard)
|
|
#
|
|
# Copyright (C) 2013 Peter Liljenberg <peter.liljenberg@gmail.com>
|
|
#
|
|
# This library is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU Lesser General Public License
|
|
# as published by the Free Software Foundation; either version 2.1
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This library is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
# See the GNU Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
# License along with this library; if not, write to the
|
|
# Free Software Foundation, Inc.,
|
|
# 59 Temple Place,
|
|
# Suite 330,
|
|
# Boston, MA 02111-1307 USA
|
|
|
|
import binascii
|
|
import sys
|
|
import os
|
|
|
|
# Change path so we find Xlib
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
|
|
from Xlib import X, display, Xutil, Xatom
|
|
|
|
|
|
def log(msg, *args):
|
|
sys.stderr.write(msg.format(*args) + '\n')
|
|
|
|
def error(msg, *args):
|
|
log(msg, *args)
|
|
sys.exit(1)
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2 or len(sys.argv) > 3:
|
|
sys.exit('usage: {0} SELECTION [TYPE]\n\n'
|
|
'SELECTION is typically PRIMARY, SECONDARY or CLIPBOARD.\n'
|
|
'If TYPE is omitted, the available types for the selction are listed.'
|
|
.format(sys.argv[0]))
|
|
|
|
d = display.Display()
|
|
|
|
sel_name = sys.argv[1]
|
|
sel_atom = d.get_atom(sel_name)
|
|
|
|
if len(sys.argv) > 2:
|
|
target_name = sys.argv[2]
|
|
target_atom = d.get_atom(target_name)
|
|
else:
|
|
target_name = 'TARGETS'
|
|
target_atom = d.get_atom('TARGETS')
|
|
|
|
# Ask the server who owns this selection, if any
|
|
owner = d.get_selection_owner(sel_atom)
|
|
|
|
if owner == X.NONE:
|
|
log('No owner for selection {0}', sel_name)
|
|
return
|
|
|
|
log('selection {0} owner: 0x{1:08x} {2}',
|
|
sel_name, owner.id, owner.get_wm_name())
|
|
|
|
# Create ourselves a window and a property for the returned data
|
|
w = d.screen().root.create_window(
|
|
0, 0, 10, 10, 0, X.CopyFromParent)
|
|
w.set_wm_name(os.path.basename(sys.argv[0]))
|
|
|
|
data_atom = d.get_atom('SEL_DATA')
|
|
|
|
# The data_atom should not be set according to ICCCM, and since
|
|
# this is a new window that is already the case here.
|
|
|
|
# Ask for the selection. We shouldn't use X.CurrentTime, but
|
|
# since we don't have an event here we have to.
|
|
w.convert_selection(sel_atom, target_atom, data_atom, X.CurrentTime)
|
|
|
|
# Wait for the notification that we got the selection
|
|
while True:
|
|
e = d.next_event()
|
|
if e.type == X.SelectionNotify:
|
|
break
|
|
|
|
# Do some sanity checks
|
|
if (e.requestor != w
|
|
or e.selection != sel_atom
|
|
or e.target != target_atom):
|
|
error('SelectionNotify event does not match our request: {0}', e)
|
|
|
|
if e.property == X.NONE:
|
|
log('selection lost or conversion to {0} failed',
|
|
target_name)
|
|
return
|
|
|
|
if e.property != data_atom:
|
|
error('SelectionNotify event does not match our request: {0}', e)
|
|
|
|
# Get the data
|
|
r = w.get_full_property(data_atom, X.AnyPropertyType,
|
|
sizehint = 10000)
|
|
|
|
# Can the data be used directly or read incrementally
|
|
if r.property_type == d.get_atom('INCR'):
|
|
log('reading data incrementally: at least {0} bytes', r.value[0])
|
|
handle_incr(d, w, data_atom, target_name)
|
|
else:
|
|
output_data(d, r, target_name)
|
|
|
|
# Tell selection owner that we're done
|
|
w.delete_property(data_atom)
|
|
|
|
|
|
def handle_incr(d, w, data_atom, target_name):
|
|
# This works by us removing the data property, the selection owner
|
|
# getting a notification of that, and then setting the property
|
|
# again with more data. To notice that, we must listen for
|
|
# PropertyNotify events.
|
|
w.change_attributes(event_mask = X.PropertyChangeMask)
|
|
|
|
while True:
|
|
# Delete data property to tell owner to give us more data
|
|
w.delete_property(data_atom)
|
|
|
|
# Wait for notification that we got data
|
|
while True:
|
|
e = d.next_event()
|
|
if (e.type == X.PropertyNotify
|
|
and e.state == X.PropertyNewValue
|
|
and e.window == w
|
|
and e.atom == data_atom):
|
|
break
|
|
|
|
r = w.get_full_property(data_atom, X.AnyPropertyType,
|
|
sizehint = 10000)
|
|
|
|
# End of data
|
|
if len(r.value) == 0:
|
|
return
|
|
|
|
output_data(d, r, target_name)
|
|
# loop around
|
|
|
|
|
|
def output_data(d, r, target_name):
|
|
log('got {0}:{1}, length {2}',
|
|
d.get_atom_name(r.property_type),
|
|
r.format,
|
|
len(r.value))
|
|
|
|
if r.format == 8:
|
|
if r.property_type == Xatom.STRING:
|
|
value = r.value.decode('ISO-8859-1')
|
|
elif r.property_type == d.get_atom('UTF8_STRING'):
|
|
value = r.value.decode('UTF-8')
|
|
else:
|
|
value = binascii.hexlify(r.value).decode('ascii')
|
|
sys.stdout.write(value)
|
|
|
|
elif r.format == 32 and r.property_type == Xatom.ATOM:
|
|
for v in r.value:
|
|
sys.stdout.write('{0}\n'.format(d.get_atom_name(v)))
|
|
|
|
else:
|
|
for v in r.value:
|
|
sys.stdout.write('{0}\n'.format(v))
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|