* Adding actors support for linux (based on old actor)

This commit is contained in:
Adolfo Gómez García 2014-11-11 07:09:53 +01:00
parent 85a32cf667
commit 418fac20e5
9 changed files with 341 additions and 32 deletions

View File

@ -33,6 +33,7 @@ from __future__ import unicode_literals
import sys import sys
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
import six
from udsactor import store from udsactor import store
from udsactor import REST from udsactor import REST
@ -48,7 +49,7 @@ class MyForm(QtGui.QDialog):
self.ui = Ui_UdsActorSetupDialog() self.ui = Ui_UdsActorSetupDialog()
self.ui.setupUi(self) self.ui.setupUi(self)
if data is not None: if data is not None:
logger.debug('Setting configuration parameters in form') logger.debug('Setting configuration parameters in form: {}'.format(data))
self.ui.host.setText(data.get('host', '')) self.ui.host.setText(data.get('host', ''))
self.ui.masterKey.setText(data.get('masterKey', '')) self.ui.masterKey.setText(data.get('masterKey', ''))
self.ui.useSSl.setCurrentIndex(1 if data.get('ssl', False) is True else 0) self.ui.useSSl.setCurrentIndex(1 if data.get('ssl', False) is True else 0)
@ -56,8 +57,8 @@ class MyForm(QtGui.QDialog):
def _getCfg(self): def _getCfg(self):
return { return {
'host': unicode(self.ui.host.text()), 'host': six.text_type(self.ui.host.text()),
'masterKey': unicode(self.ui.masterKey.text()), 'masterKey': six.text_type(self.ui.masterKey.text()),
'ssl': self.ui.useSSl.currentIndex() == 1, 'ssl': self.ui.useSSl.currentIndex() == 1,
'logLevel': (self.ui.logLevelComboBox.currentIndex() + 1) * 10000 'logLevel': (self.ui.logLevelComboBox.currentIndex() + 1) * 10000
} }
@ -104,7 +105,7 @@ if __name__ == "__main__":
cfg = store.readConfig() cfg = store.readConfig()
if cfg is not None: if cfg is not None:
logger.setLevel(cfg.get('logLevel', 20000)) logger.setLevel(int(cfg.get('logLevel', 20000)))
else: else:
logger.setLevel(20000) logger.setLevel(20000)

View File

@ -209,8 +209,8 @@ def testRemote():
if __name__ == '__main__': if __name__ == '__main__':
# ipcServer() # ipcServer()
ipcTest() # ipcTest()
# testRest() testRest()
# testIdle() # testIdle()
# testServer() # testServer()
# testRemote() # testRemote()

View File

@ -29,14 +29,14 @@
''' '''
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
''' '''
from __future__ import unicode_literals
import SimpleHTTPServer
import SocketServer
import threading import threading
import uuid import uuid
import json import json
from base64 import decodestring import six
from six.moves import socketserver # @UnresolvedImport, pylint: disable=import-error
from six.moves import SimpleHTTPServer # @UnresolvedImport, pylint: disable=import-error
import time import time
from udsactor.log import logger from udsactor.log import logger
@ -59,7 +59,6 @@ class HTTPServerHandler(SimpleHTTPServer.BaseHTTPServer.BaseHTTPRequestHandler):
self.wfile.write(json.dumps({'error': message})) self.wfile.write(json.dumps({'error': message}))
return return
def do_GET(self): def do_GET(self):
# Very simple path & params splitter # Very simple path & params splitter
path = self.path.split('?')[0][1:].split('/') path = self.path.split('?')[0][1:].split('/')
@ -88,7 +87,7 @@ class HTTPServerHandler(SimpleHTTPServer.BaseHTTPServer.BaseHTTPRequestHandler):
return return
except Exception as e: except Exception as e:
logger.error('Got exception executing GET {}: {}'.format(path[1], utils.toUnicode(e.message))) logger.error('Got exception executing GET {}: {}'.format(path[1], utils.toUnicode(e.message)))
self.sendJsonError(500, unicode(e)) self.sendJsonError(500, str(e))
return return
self.send_response(200) self.send_response(200)
@ -111,7 +110,7 @@ class HTTPServerHandler(SimpleHTTPServer.BaseHTTPServer.BaseHTTPRequestHandler):
HTTPServerHandler.lock.acquire() HTTPServerHandler.lock.acquire()
length = int(self.headers.getheader('content-length')) length = int(self.headers.getheader('content-length'))
content = self.rfile.read(length) content = self.rfile.read(length)
print length, ">>", content, '<<' print(length, ">>", content, '<<')
params = json.loads(content) params = json.loads(content)
operation = getattr(self, 'post_' + path[1]) operation = getattr(self, 'post_' + path[1])
@ -121,12 +120,11 @@ class HTTPServerHandler(SimpleHTTPServer.BaseHTTPServer.BaseHTTPRequestHandler):
return return
except Exception as e: except Exception as e:
logger.error('Got exception executing POST {}: {}'.format(path[1], utils.toUnicode(e.message))) logger.error('Got exception executing POST {}: {}'.format(path[1], utils.toUnicode(e.message)))
self.sendJsonError(500, unicode(e)) self.sendJsonError(500, str(e))
return return
finally: finally:
HTTPServerHandler.lock.release() HTTPServerHandler.lock.release()
self.send_response(200) self.send_response(200)
self.send_header('Content-type', 'application/json') self.send_header('Content-type', 'application/json')
self.end_headers() self.end_headers()
@ -158,10 +156,11 @@ class HTTPServerHandler(SimpleHTTPServer.BaseHTTPServer.BaseHTTPRequestHandler):
# Execute script at server space, that is, here # Execute script at server space, that is, here
# as a secondary thread # as a secondary thread
script = params['script'] script = params['script']
def executor(): def executor():
logger.debug('Executing script: {}'.format(script)) logger.debug('Executing script: {}'.format(script))
try: try:
exec script in None, None exec(script, None, None)
except Exception as e: except Exception as e:
logger.error('Error executing script: {}'.format(e)) logger.error('Error executing script: {}'.format(e))
th = threading.Thread(target=executor) th = threading.Thread(target=executor)
@ -183,7 +182,7 @@ class HTTPServerThread(threading.Thread):
HTTPServerHandler.ipc = ipc HTTPServerHandler.ipc = ipc
self.certFile = createSelfSignedCert() self.certFile = createSelfSignedCert()
self.server = SocketServer.TCPServer(address, HTTPServerHandler) self.server = socketserver.TCPServer(address, HTTPServerHandler)
self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.certFile, server_side=True) self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.certFile, server_side=True)
def getServerUrl(self): def getServerUrl(self):

View File

@ -1 +0,0 @@

View File

@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import socket
import platform
import fcntl
import os
import struct
import array
import six
from udsactor import utils
def _getMacAddr(ifname):
'''
Returns the mac address of an interface
Mac is returned as unicode utf-8 encoded
'''
if isinstance(ifname, list):
return dict([(name, _getMacAddr(name)) for name in ifname])
if isinstance(ifname, six.text_type):
ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15])))
return six.text_type(''.join(['%02x:' % char for char in info[18:24]])[:-1])
except Exception:
return None
def _getIpAddr(ifname):
'''
Returns the ip address of an interface
Ip is returned as unicode utf-8 encoded
'''
if isinstance(ifname, list):
return dict([(name, _getIpAddr(name)) for name in ifname])
if isinstance(ifname, six.text_type):
ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return six.text_type(socket.inet_ntoa(fcntl.ioctl(
s.fileno(),
0x8915, # SIOCGIFADDR
struct.pack('256s', ifname[:15])
)[20:24]))
except Exception:
return None
def _getInterfaces():
'''
Returns a list of interfaces names coded in utf-8
'''
max_possible = 128 # arbitrary. raise if needed.
space = max_possible * 16
if platform.architecture()[0] == '32bit':
offset, length = 32, 32
elif platform.architecture()[0] == '64bit':
offset, length = 16, 40
else:
raise OSError('Unknown arquitecture {0}'.format(platform.architecture()[0]))
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
names = array.array(str('B'), b'\0' * space)
outbytes = struct.unpack('iL', fcntl.ioctl(
s.fileno(),
0x8912, # SIOCGIFCONF
struct.pack('iL', space, names.buffer_info()[0])
))[0]
namestr = names.tostring()
# return namestr, outbytes
return [namestr[i:i + offset].split(b'\0', 1)[0].decode('utf-8') for i in range(0, outbytes, length)]
def _getIpAndMac(ifname):
ip, mac = _getIpAddr(ifname), _getMacAddr(ifname)
return (ip, mac)
def getComputerName():
'''
Returns computer name, with no domain
'''
return socket.gethostname().split('.')[0]
def getNetworkInfo():
for ifname in _getInterfaces():
ip, mac = _getIpAndMac(ifname)
yield utils.Bunch(name=ifname, mac=mac, ip=ip)
def getDomainName():
return ''
def getLinuxVersion():
lv = platform.linux_distribution()
return lv[0] + ', ' + lv[1]
def reboot(flags=0):
'''
Simple reboot using os command
'''
os.system('/sbin/shutdown now -r')
def loggoff():
pass
def renameComputer(newName):
pass
def joinDomain(domain, ou, account, password, executeInOneStep=False):
pass
def changeUserPassword(user, oldPassword, newPassword):
'''
Simple password change for user using command line
'''
os.system('echo "{1}\n{1}" | /usr/bin/passwd {0} 2> /dev/null'.format(user, newPassword))
def getIdleDuration():
return 0

View File

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import platform
import os
import sys
import pkgutil
from udsactor.log import logger
renamers = {}
def rename(newName):
distribution = platform.linux_distribution()[0].lower()
if distribution in renamers:
return renamers[distribution](newName)
logger.error('Renamer for platform "{0}" not found'.format(distribution))
return False
# Do load of packages
def _init():
pkgpath = os.path.dirname(sys.modules[__name__].__file__)
for _, name, _ in pkgutil.iter_modules([pkgpath]):
__import__(__name__ + '.' + name, globals(), locals())
_init()

View File

@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from udsactor.linux.renamer import renamers
from udsactor.log import logger
import os
def rename(newName):
# If new name has "'\t'
if '\t' in newName:
newName, account, password = newName.split('\t')
else:
account = password = None
logger.debug('Debian renamer')
if account is not None:
os.system('echo "{1}\n{1}" | /usr/bin/passwd {0} 2> /dev/null'.format(account, password))
f = open('/etc/hostname', 'w')
f.write(newName)
f.close()
os.system('/bin/hostname %s' % newName)
# add name to "hosts"
f = open('/etc/hosts', 'r')
lines = f.readlines()
f.close()
f = open('/etc/hosts', 'w')
f.write("127.0.1.1\t%s\n" % newName)
for l in lines:
if l[:9] == '127.0.1.1':
continue
f.write(l)
f.close()
return True
# All names in lower case
renamers['debian'] = rename
renamers['ubuntu'] = rename

View File

@ -29,7 +29,7 @@
''' '''
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
''' '''
from __future__ import unicode_literals
import six import six
import os import os
@ -47,10 +47,15 @@ def readConfig():
res = {} res = {}
try: try:
cfg = six.moves.configparser.SafeConfigParser() # @UndefinedVariable cfg = six.moves.configparser.SafeConfigParser() # @UndefinedVariable
cfg.optionxform = six.text_type
cfg.read(CONFIGFILE) cfg.read(CONFIGFILE)
# Just reads 'uds' section # Just reads 'uds' section
for key in cfg.options('uds'): for key in cfg.options('uds'):
res[key] = cfg.get('uds', key) res[key] = cfg.get('uds', key)
if res[key].lower() in ('true', 'yes', 'si'):
res[key] = True
elif res[key].lower() in ('false', 'no'):
res[key] = False
except Exception: except Exception:
pass pass
@ -59,10 +64,12 @@ def readConfig():
def writeConfig(data): def writeConfig(data):
cfg = six.moves.configparser.SafeConfigParser() # @UndefinedVariable cfg = six.moves.configparser.SafeConfigParser() # @UndefinedVariable
cfg.optionxform = six.text_type
cfg.add_section('uds') cfg.add_section('uds')
for key, val in data.iteritems(): for key, val in data.items():
cfg.set('uds', key, unicode(val)) cfg.set('uds', key, str(val))
with file(CONFIGFILE, 'w') as f:
with open(CONFIGFILE, 'w') as f:
cfg.write(f) cfg.write(f)
os.chmod(CONFIGFILE, 0600) os.chmod(CONFIGFILE, 0o0600)

View File

@ -31,24 +31,27 @@
''' '''
from __future__ import unicode_literals from __future__ import unicode_literals
import win32com.client import win32com.client # @UnresolvedImport, pylint: disable=import-error
import win32net import win32net # @UnresolvedImport, pylint: disable=import-error
import win32security import win32security # @UnresolvedImport, pylint: disable=import-error
import win32api import win32api # @UnresolvedImport, pylint: disable=import-error
import win32con import win32con # @UnresolvedImport, pylint: disable=import-error
import ctypes import ctypes
from ctypes.wintypes import DWORD, LPCWSTR from ctypes.wintypes import DWORD, LPCWSTR
from udsactor import utils from udsactor import utils
from udsactor.log import logger from udsactor.log import logger
def getErrorMessage(res=0): def getErrorMessage(res=0):
msg = win32api.FormatMessage(res) msg = win32api.FormatMessage(res)
return msg.decode('windows-1250', 'ignore') return msg.decode('windows-1250', 'ignore')
def getComputerName(): def getComputerName():
return win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname) return win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname)
def getNetworkInfo(): def getNetworkInfo():
obj = win32com.client.Dispatch("WbemScripting.SWbemLocator") obj = win32com.client.Dispatch("WbemScripting.SWbemLocator")
wmobj = obj.ConnectServer("localhost", "root\cimv2") wmobj = obj.ConnectServer("localhost", "root\cimv2")
@ -64,6 +67,7 @@ def getNetworkInfo():
except Exception: except Exception:
return return
def getDomainName(): def getDomainName():
''' '''
Will return the domain name if we belong a domain, else None Will return the domain name if we belong a domain, else None
@ -80,6 +84,7 @@ def getDomainName():
return domain return domain
def getWindowsVersion(): def getWindowsVersion():
return win32api.GetVersionEx() return win32api.GetVersionEx()
@ -90,6 +95,7 @@ EWX_FORCE = 0x00000004
EWX_POWEROFF = 0x00000008 EWX_POWEROFF = 0x00000008
EWX_FORCEIFHUNG = 0x00000010 EWX_FORCEIFHUNG = 0x00000010
def reboot(flags=EWX_FORCEIFHUNG | EWX_REBOOT): def reboot(flags=EWX_FORCEIFHUNG | EWX_REBOOT):
hproc = win32api.GetCurrentProcess() hproc = win32api.GetCurrentProcess()
htok = win32security.OpenProcessToken(hproc, win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY) htok = win32security.OpenProcessToken(hproc, win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY)
@ -101,6 +107,7 @@ def reboot(flags=EWX_FORCEIFHUNG | EWX_REBOOT):
def loggoff(): def loggoff():
win32api.ExitWindowsEx(EWX_LOGOFF) win32api.ExitWindowsEx(EWX_LOGOFF)
def renameComputer(newName): def renameComputer(newName):
# Needs admin privileges to work # Needs admin privileges to work
if ctypes.windll.kernel32.SetComputerNameExW(DWORD(win32con.ComputerNamePhysicalDnsHostname), LPCWSTR(newName)) == 0: if ctypes.windll.kernel32.SetComputerNameExW(DWORD(win32con.ComputerNamePhysicalDnsHostname), LPCWSTR(newName)) == 0:
@ -121,6 +128,7 @@ NETSETUP_MACHINE_PWD_PASSED = 0x00000080
NETSETUP_JOIN_WITH_NEW_NAME = 0x00000400 NETSETUP_JOIN_WITH_NEW_NAME = 0x00000400
NETSETUP_DEFER_SPN_SET = 0x1000000 NETSETUP_DEFER_SPN_SET = 0x1000000
def joinDomain(domain, ou, account, password, executeInOneStep=False): def joinDomain(domain, ou, account, password, executeInOneStep=False):
# If account do not have domain, include it # If account do not have domain, include it
if '@' not in account and '\\' not in account: if '@' not in account and '\\' not in account:
@ -156,6 +164,7 @@ def joinDomain(domain, ou, account, password, executeInOneStep=False):
print res, error print res, error
raise Exception('Error joining domain {}, with credentials {}/*****{}: {}, {}'.format(domain.value, account.value, ', under OU {}'.format(ou.value) if ou.value != None else '', res, error)) raise Exception('Error joining domain {}, with credentials {}/*****{}: {}, {}'.format(domain.value, account.value, ', under OU {}'.format(ou.value) if ou.value != None else '', res, error))
def changeUserPassword(user, oldPassword, newPassword): def changeUserPassword(user, oldPassword, newPassword):
computerName = LPCWSTR(getComputerName()) computerName = LPCWSTR(getComputerName())
user = LPCWSTR(user) user = LPCWSTR(user)
@ -169,12 +178,14 @@ def changeUserPassword(user, oldPassword, newPassword):
error = getErrorMessage() error = getErrorMessage()
raise Exception('Error changing password for user {}: {}'.format(user.value, error)) raise Exception('Error changing password for user {}: {}'.format(user.value, error))
class LASTINPUTINFO(ctypes.Structure): class LASTINPUTINFO(ctypes.Structure):
_fields_ = [ _fields_ = [
('cbSize', ctypes.c_uint), ('cbSize', ctypes.c_uint),
('dwTime', ctypes.c_uint), ('dwTime', ctypes.c_uint),
] ]
def getIdleDuration(): def getIdleDuration():
lastInputInfo = LASTINPUTINFO() lastInputInfo = LASTINPUTINFO()
lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo) lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo)