#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"Sets the X cursor theme, either once or at a regular interval."
# Copyright (c) 2016 Scott Zeid .
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License version 2 as published by the Free Software Foundation.
#
# This program 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
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
# Portions of this program are translated from portions of
# kde-workspace (/plasma-desktop/kcms/input/xcursor/cursortheme.cpp) and
# plasma-desktop (/kcms/cursortheme/xcursor/xcursortheme.cpp)
# that were originally under the following license:
# Copyright © 2006-2007 Fredrik Höglund
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License version 2 or at your option version 3 as published
# by the Free Software Foundation.
#
# This program 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
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
# Portions of this program are translated from portions of
# plasma-desktop (/kcms/cursortheme/xcursor/themepage.cpp)
# that were originally under the following license:
# Copyright © 2003-2007 Fredrik Höglund
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License version 2 as published by the Free Software Foundation.
#
# This program 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
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
import argparse
import os,signal
import sys
import time
import subprocess
from ctypes import *
pidFile="/tmp/.setcursor"
if os.path.isfile(pidFile):
with open(pidFile,"r") as f:
fpid=f.read()
try:
os.kill(int(fpid),signal.SIGKILL)
except Exception as e:
print(e)
pass
with open(pidFile,"w") as f:
f.write(str(os.getpid()))
try:
os.chmod(pidFile,0o666)
except:
pass
l_X11 = cdll.LoadLibrary("libX11.so")
t_XID = c_ulong
t_Cursor = t_XID
t_Display_p = POINTER(c_int)
#XOpenDisplay
XOpenDisplay = l_X11.XOpenDisplay
XOpenDisplay.argtypes = [c_char_p]
XOpenDisplay.restype = t_Display_p
#XFreeCursor
XFreeCursor = l_X11.XFreeCursor
XFreeCursor.argtypes = [t_Display_p, t_Cursor]
XFreeCursor.restype = c_int
try:
l_Xcursor = cdll.LoadLibrary("libXcursor.so")
except OSError:
l_Xcursor = cdll.LoadLibrary("libXcursor.so.1")
t_XcursorImages_p = POINTER(c_int)
#XcursorLibraryLoadImages
XcursorLibraryLoadImages = l_Xcursor.XcursorLibraryLoadImages
XcursorLibraryLoadImages.argtypes = [c_char_p, c_char_p, c_int]
XcursorLibraryLoadImages.restype = t_XcursorImages_p
#XcursorImagesLoadCursor
XcursorImagesLoadCursor = l_Xcursor.XcursorImagesLoadCursor
XcursorImagesLoadCursor.argtypes = [t_Display_p, t_XcursorImages_p]
XcursorImagesLoadCursor.restype = t_Cursor
#XcursorImagesDestroy
XcursorImagesDestroy = l_Xcursor.XcursorImagesDestroy
XcursorImagesDestroy.argtypes = [t_XcursorImages_p]
l_Xfixes = cdll.LoadLibrary("libXfixes.so")
#XFixesSetCursorName
XFixesSetCursorName = l_Xfixes.XFixesSetCursorName
XFixesSetCursorName.argtypes = [t_Display_p, t_Cursor, c_char_p]
#XFixesChangeCursorByName
XFixesChangeCursorByName = l_Xfixes.XFixesChangeCursorByName
XFixesChangeCursorByName.argtypes = [t_Display_p, t_Cursor, c_char_p]
CURSOR_LIST = None # this is set near the end of the file
def main(argv):
error = os.path.basename(argv[0]) + ": error: %s"
p = argparse.ArgumentParser(description=__doc__.splitlines()[0].strip())
p.add_argument("--repeat", "-r", metavar="N", type=float, default=0,
help="repeat every N seconds (default or 0: do not repeat)")
p.add_argument("theme",
help="the theme's name")
p.add_argument("size", type=int, nargs="?", default=0,
help="the size to use (default: 0)")
try:
args = p.parse_args(argv[1:])
except SystemExit as exc:
return exc.code
display = XOpenDisplay(None)
if not display:
print >> sys.stderr, error % "could not open X display"
return 1
home=os.environ.get('HOME')
cmd=["xrdb","-merge",os.path.join(home,".Xdefaults")]
subprocess.call(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
cmd=["qdbus","org.kde.klauncher5","/KLauncher","org.kde.KLauncher.setLaunchEnv","XCURSOR_SIZE","{}".format(args.size)]
subprocess.call(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
cmd=["qdbus","org.kde.klauncher5","/KLauncher","org.kde.KLauncher.setLaunchEnv","XCURSOR_THEME","{}".format(args.theme)]
subprocess.call(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
gtkFiles="/etc/gtk/gtkrc:{0}/.gtkrc:{0}/.config/gtkrc".format(home)
cmd=["qdbus","org.kde.klauncher5","/KLauncher","org.kde.KLauncher.setLaunchEnv","GTK_RC_FILES","{}".format(gtkFiles)]
subprocess.call(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
#cmd=["qdbus","org.kde.klauncher5","/KLauncher","org.kde.KLauncher.setLaunchEnv","GTK_RC_FILES","{}".format("/etc/gtk/gtkrc:/home/juanma/.gtkrc:/home/juanma/.config/gtkrc")]
#subprocess.call(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
gtk2Files="/etc/gtk-2.0/gtkrc:{0}/.gtkrc-2.0:{0}/.config/gtkrc-2.0".format(home)
cmd=["qdbus","org.kde.klauncher5","/KLauncher","org.kde.KLauncher.setLaunchEnv","GTK2_RC_FILES","{}".format(gtk2Files)]
subprocess.call(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
#cmd=["qdbus","org.kde.klauncher5","/KLauncher","org.kde.KLauncher.setLaunchEnv","GTK2_RC_FILES","{}".format("/etc/gtk-2.0/gtkrc:/home/juanma/.gtkrc-2.0:/home/juanma/.config/gtkrc-2.0")]
#subprocess.call(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
while True:
set_cursor_theme(display, args.theme, args.size)
if args.repeat:
time.sleep(args.repeat)
else:
break
def load_images(display, name, theme, size):
print(name)
print(name.encode())
images = XcursorLibraryLoadImages(name.encode(), theme.encode(), size)
if images:
return images
def load_cursor(display, name, images):
cursor = XcursorImagesLoadCursor(display, images)
#XcursorImagesDestroy(images)
XFixesSetCursorName(display, cursor, name.encode())
return cursor
def set_cursor_theme(display, theme, size):
for cursor in CURSOR_LIST:
if cursor.images is None:
cursor.images = load_images(display, cursor.name, theme, size)
if not cursor.images and cursor.alternative:
cursor.images = load_images(display, cursor.alternative, theme, size)
if not cursor.images:
cursor.images = False
if cursor.images:
cursor_xid = load_cursor(display, cursor.name, cursor.images)
XFixesChangeCursorByName(display, cursor_xid, cursor.name.encode())
XFreeCursor(display, cursor_xid)
def make_cursor_list():
cursor_list_file = """
# From ThemePage::applyTheme
# and XCursorTheme::findAlternative
# First column is cursor name; optional second column is alternative name
# Qt
left_ptr
up_arrow center_ptr
cross crosshair
wait watch
left_ptr_watch 3ecb610c1bf2410f44200f48c40d3599
ibeam xterm
size_ver 00008160000006810000408080010102
size_hor 028006030e0e7ebffc7f7070c0600140
size_bdiag fcf1c3c7cd4491d801f1e1c78f100000
size_fdiag c7088f0f3e6c8088236ef8e1e3e70000
size_all fleur
split_v 2870a09082c103050810ffdffffe0204
split_h 14fef782d02440884392942c11205230
pointing_hand hand2
openhand 9141b49c8149039304290b508d208c40
closedhand 05e88622050804100c20044008402080
forbidden 03b6e0fcb3499374a867c041f52298f0
whats_this d9ce0ab605698f320427677b458ad60b
copy
move
link
# X core
X_cursor
right_ptr
hand1
hand2 e29285e634086352946a0e7090d73106
watch
xterm
crosshair
left_ptr_watch
center_ptr
sb_h_double_arrow
sb_v_double_arrow
fleur
top_left_corner
top_side
top_right_corner
right_side
bottom_right_corner
bottom_side
bottom_left_corner
left_side
question_arrow
pirate
""".lstrip()
cursor_list = [i for i in cursor_list_file.strip().replace("#", "\n#").splitlines()
if i and not i.startswith("#")]
for i in range(len(cursor_list)):
if " " in cursor_list[i]:
name, alternative = cursor_list[i].split(" ", 2)
else:
name, alternative = cursor_list[i], None
cursor_list[i] = Cursor(name, alternative)
return cursor_list
class Cursor(object):
def __init__(self, name, alternative=None):
self.name = name
self.alternative = alternative
#: t_XcursorImages_p (X11/Xcursor/Xcursor.h:XcursorImages*)
#: None: uninitialized; False: does not exist, don't try to initialize again
self.images = None
CURSOR_LIST = make_cursor_list()
if __name__ == "__main__":
try:
sys.exit(main(sys.argv))
except KeyboardInterrupt:
pass