856 lines
24 KiB
Lua
856 lines
24 KiB
Lua
local glue = require'glue'
|
|
local objc = require'objc'
|
|
local ffi = require'ffi'
|
|
local pp = require'pp'
|
|
|
|
io.stdout:setvbuf'no'
|
|
io.stderr:setvbuf'no'
|
|
|
|
setmetatable(_G, {__index = objc})
|
|
|
|
--test options
|
|
|
|
local subprocess = false --run each bridgesupport test in a subprocess
|
|
objc.debug.lazyfuncs = true
|
|
objc.debug.checkredef = false
|
|
objc.debug.printcdecl = false
|
|
objc.debug.loaddeps = false
|
|
objc.debug.loadtypes = true
|
|
|
|
local bsdir = '_bridgesupport' --path where *.bridgesupport files are on Windows (tree or flat doesn't matter)
|
|
local luajit = ffi.os == 'Windows' and 'luajit' or './luajit' --luajit command for subprocess running
|
|
|
|
if ffi.os == 'OSX' then
|
|
objc.load'Foundation'
|
|
pool = NSAutoreleasePool:new()
|
|
end
|
|
|
|
--test helpers
|
|
|
|
local function printf(...)
|
|
print(string.format(...))
|
|
end
|
|
|
|
local function hr()
|
|
print(('-'):rep(80))
|
|
end
|
|
|
|
local n = 0
|
|
local function genname(prefix)
|
|
if not prefix then return genname'MyClass' end
|
|
n = n + 1
|
|
return prefix..n
|
|
end
|
|
|
|
local function errpcall(patt, ...) --pcall that should fail with a specific message
|
|
local ok, err = pcall(...)
|
|
assert(not ok)
|
|
assert(err:find(patt))
|
|
end
|
|
|
|
--test namespace
|
|
|
|
local test = {} --{name = test_func}
|
|
local eyetest = {} --{name = test_func}
|
|
local demo = {}
|
|
local tests = {tests = test, ['eye tests'] = eyetest, demos = demo}
|
|
|
|
function test.parsing()
|
|
assert(stype_ctype('[8^c]', 'arr') == 'char *arr[8]') --array of pointers
|
|
assert(stype_ctype('^[8c]', 'arr') == 'char (*arr)[8]') --pointer to array
|
|
assert(stype_ctype('[8[4c]]', 'arr') == 'char arr[8][4]') --multi-dim. array
|
|
assert(stype_ctype('[3^[8^c]]', 'arr') == 'char *(*arr[3])[8]')
|
|
assert(stype_ctype('{?="x"i"y"i""(?="ux"I"uy"I)}', nil, 'cdef') ==
|
|
'struct {\n\tint x;\n\tint y;\n\tunion {\n\t\tunsigned int ux;\n\t\tunsigned int uy;\n\t};\n}'
|
|
) --nested unnamed anonymous structs
|
|
|
|
local function mtype_ctype(mtype, ...)
|
|
return ftype_ctype(mtype_ftype(mtype), ...)
|
|
end
|
|
assert(mtype_ctype('@"Class"@:{_NSRect={_NSPoint=ff}{_NSSize=ff}}^{?}^?', 'globalFunction') ==
|
|
'id globalFunction (id, SEL, struct _NSRect, void *, void *)') --unseparated method args
|
|
assert(mtype_ctype('{_NSPoint=ff}iii', nil, true) ==
|
|
'void (*) (int, int, int)') --struct return value not supported
|
|
assert(mtype_ctype('iii{_NSPoint=ff}ii', nil, true) ==
|
|
'int (*) (int, int)') --pass-by-value struct not supported, stop at first encounter
|
|
assert(mtype_ctype('{_NSPoint=ff}ii{_NSPoint=ff}i', nil, true) ==
|
|
'void (*) (int, int)') --combined case
|
|
end
|
|
|
|
function eyetest.indent()
|
|
--_NXEvent (test indent for nested unnamed anonymous structs)
|
|
print(stype_ctype('{?="type"i"location"{?="x"i"y"i}"time"Q"flags"i"window"I"service_id"Q"ext_pid"i"data"(?="mouse"{?="subx"C"suby"C"eventNum"s"click"i"pressure"C"buttonNumber"C"subType"C"reserved2"C"reserved3"i"tablet"(?="point"{_NXTabletPointData="x"i"y"i"z"i"buttons"S"pressure"S"tilt"{?="x"s"y"s}"rotation"S"tangentialPressure"s"deviceID"S"vendor1"s"vendor2"s"vendor3"s}"proximity"{_NXTabletProximityData="vendorID"S"tabletID"S"pointerID"S"deviceID"S"systemTabletID"S"vendorPointerType"S"pointerSerialNumber"I"uniqueID"Q"capabilityMask"I"pointerType"C"enterProximity"C"reserved1"s})}"mouseMove"{?="dx"i"dy"i"subx"C"suby"C"subType"C"reserved1"C"reserved2"i"tablet"(?="point"{_NXTabletPointData="x"i"y"i"z"i"buttons"S"pressure"S"tilt"{?="x"s"y"s}"rotation"S"tangentialPressure"s"deviceID"S"vendor1"s"vendor2"s"vendor3"s}"proximity"{_NXTabletProximityData="vendorID"S"tabletID"S"pointerID"S"deviceID"S"systemTabletID"S"vendorPointerType"S"pointerSerialNumber"I"uniqueID"Q"capabilityMask"I"pointerType"C"enterProximity"C"reserved1"s})}"key"{?="origCharSet"S"repeat"s"charSet"S"charCode"S"keyCode"S"origCharCode"S"reserved1"i"keyboardType"I"reserved2"i"reserved3"i"reserved4"i"reserved5"[4i]}"tracking"{?="reserved"s"eventNum"s"trackingNum"i"userData"i"reserved1"i"reserved2"i"reserved3"i"reserved4"i"reserved5"i"reserved6"[4i]}"scrollWheel"{?="deltaAxis1"s"deltaAxis2"s"deltaAxis3"s"reserved1"s"fixedDeltaAxis1"i"fixedDeltaAxis2"i"fixedDeltaAxis3"i"pointDeltaAxis1"i"pointDeltaAxis2"i"pointDeltaAxis3"i"reserved8"[4i]}"zoom"{?="deltaAxis1"s"deltaAxis2"s"deltaAxis3"s"reserved1"s"fixedDeltaAxis1"i"fixedDeltaAxis2"i"fixedDeltaAxis3"i"pointDeltaAxis1"i"pointDeltaAxis2"i"pointDeltaAxis3"i"reserved8"[4i]}"compound"{?="reserved"s"subType"s"misc"(?="F"[11f]"L"[11i]"S"[22s]"C"[44c])}"tablet"{?="x"i"y"i"z"i"buttons"S"pressure"S"tilt"{?="x"s"y"s}"rotation"S"tangentialPressure"s"deviceID"S"vendor1"s"vendor2"s"vendor3"s"reserved"[4i]}"proximity"{?="vendorID"S"tabletID"S"pointerID"S"deviceID"S"systemTabletID"S"vendorPointerType"S"pointerSerialNumber"I"uniqueID"Q"capabilityMask"I"pointerType"C"enterProximity"C"reserved1"s"reserved2"[4i]})}', nil, 'cdef'))
|
|
end
|
|
|
|
function eyetest.tostring()
|
|
print(objc.NSNumber:numberWithDouble(0))
|
|
print(objc.NSString:alloc():initWithUTF8String'') --empty string = NSFConstantString with hi address
|
|
print(objc.NSString:alloc():initWithUTF8String'asdjfah') --real object
|
|
end
|
|
|
|
--test parsing of bridgesupport files.
|
|
--works on Windows too - just copy your bridgesupport files into whatever you set `bsdir` above.
|
|
function eyetest.bridgesupport(bsfile)
|
|
|
|
local function list_func(cmd)
|
|
return function()
|
|
return coroutine.wrap(function()
|
|
local f = io.popen(cmd)
|
|
for s in f:lines() do
|
|
coroutine.yield(s)
|
|
end
|
|
f:close()
|
|
end)
|
|
end
|
|
end
|
|
|
|
local bsfiles
|
|
if ffi.os == 'Windows' then
|
|
bsfiles = list_func('dir /B /S '..bsdir..'\\*.bridgesupport')
|
|
elseif ffi.os == 'OSX' then
|
|
bsfiles = list_func('find /System/Library/frameworks -name \'*.bridgesupport\'')
|
|
else
|
|
error'can\'t run on this OS'
|
|
end
|
|
|
|
local loaded = {}
|
|
local n = 0
|
|
|
|
local objc_load = objc.debug.load_framework --keep it, we'll patch it
|
|
|
|
function objc.debug.load_framework(path) --either `name.bridgesupport` or `name.framework` or `name.framework/name`
|
|
local name
|
|
if path:match'%.bridgesupport$' then
|
|
name = path:match'([^/\\]+)%.bridgesupport$'
|
|
else
|
|
name = path:match'/([^/]+)%.framework$' or path:match'([^/]+)$'
|
|
if ffi.os == 'Windows' then
|
|
path = bsdir..'\\'..name..'.bridgesupport'
|
|
else
|
|
path = path .. '/Resources/BridgeSupport/' .. name .. '.bridgesupport'
|
|
end
|
|
end
|
|
if loaded[name] then return end
|
|
loaded[name] = true
|
|
if glue.canopen(path) then
|
|
|
|
if ffi.os == 'OSX' then
|
|
|
|
--load the dylib first (needed for function aliases)
|
|
local dpath = path:gsub('Resources/BridgeSupport/.*$', name)
|
|
if glue.canopen(dpath) then
|
|
pcall(ffi.load, dpath, true)
|
|
end
|
|
|
|
--load the dylib with inlines first (needed for function aliases)
|
|
local dpath = path:gsub('bridgesupport$', 'dylib')
|
|
if glue.canopen(dpath) then
|
|
pcall(ffi.load, dpath, true)
|
|
end
|
|
end
|
|
|
|
objc.debug.load_bridgesupport(path)
|
|
n = n + 1
|
|
--print(n, '', name)
|
|
else
|
|
print('! not found', name, path)
|
|
end
|
|
end
|
|
|
|
local function status()
|
|
pp('errors', objc.debug.errcount)
|
|
print('globals: '..objc.debug.cnames.global[1])
|
|
print('structs: '..objc.debug.cnames.struct[1])
|
|
end
|
|
|
|
if bsfile then
|
|
objc.debug.load_framework(bsfile)
|
|
else
|
|
for bsfile in bsfiles() do
|
|
if bsfile:match'Python' then
|
|
print('skipping '..bsfile) --python bridgesupport files are non-standard and deprecated
|
|
else
|
|
--print(); print(bsfile); print(('='):rep(80))
|
|
if subprocess then
|
|
os.execute(luajit..' '..arg[0]..' bridgesupport '..bsfile)
|
|
else
|
|
objc.debug.load_framework(bsfile)
|
|
end
|
|
end
|
|
end
|
|
status()
|
|
end
|
|
|
|
objc.debug.load_framework = objc_load --put it back
|
|
end
|
|
|
|
function test.selectors()
|
|
assert(tostring(SEL'se_lec_tor') == 'se:lec:tor')
|
|
assert(tostring(SEL'se_lec_tor_') == 'se:lec:tor:')
|
|
assert(tostring(SEL'__se_lec_tor') == '__se:lec:tor')
|
|
assert(tostring(SEL'__se:lec:tor:') == '__se:lec:tor:')
|
|
end
|
|
|
|
--class, superclass, metaclass, class protocols
|
|
function test.class()
|
|
--arg. checking
|
|
errpcall('already', class, 'NSObject', 'NSString')
|
|
errpcall('superclass', class, genname(), 'MyUnknownClass')
|
|
errpcall('protocol', class, genname(), 'NSObject <MyUnknownProtocol>')
|
|
|
|
--class overloaded constructors
|
|
local cls = class('MyClassX', false) --root class
|
|
assert(classname(cls) == 'MyClassX')
|
|
assert(not superclass(cls))
|
|
|
|
--derived class
|
|
local cls = class(genname(), 'NSArray')
|
|
assert(isa(cls, 'NSArray'))
|
|
|
|
--derived + conforming
|
|
local cls = class(genname(), 'NSArray <NSStreamDelegate, NSLocking>')
|
|
assert(isa(cls, 'NSArray'))
|
|
|
|
assert(conforms(cls, 'NSStreamDelegate'))
|
|
assert(conforms(cls, 'NSLocking'))
|
|
|
|
local t = {0}
|
|
for proto in protocols(cls) do
|
|
t[proto:name()] = true
|
|
t[1] = t[1] + 1
|
|
end
|
|
assert(t[1] == 2)
|
|
assert(t.NSStreamDelegate)
|
|
assert(t.NSLocking)
|
|
|
|
--class hierarchy queries
|
|
assert(superclass(cls) == NSArray)
|
|
assert(metaclass(cls))
|
|
assert(superclass(metaclass(cls)) == metaclass'NSArray')
|
|
assert(metaclass(superclass(cls)) == metaclass'NSArray')
|
|
assert(metaclass(metaclass(cls)) == nil)
|
|
assert(isa(cls, 'NSObject'))
|
|
assert(ismetaclass(metaclass(cls)))
|
|
assert(isclass(cls))
|
|
assert(not ismetaclass(cls))
|
|
assert(not isobj(cls))
|
|
assert(isclass(metaclass(cls)))
|
|
|
|
local obj = cls:new()
|
|
assert(isobj(obj))
|
|
assert(not isclass(obj))
|
|
end
|
|
|
|
function test.refcount()
|
|
local cls = class(genname(), 'NSObject')
|
|
local inst, inst2, inst3
|
|
|
|
inst = cls:new()
|
|
assert(inst:retainCount() == 1)
|
|
|
|
inst2 = inst:retain() --same class, new cdata, new reference
|
|
assert(inst:retainCount() == 2)
|
|
|
|
inst3 = inst:retain()
|
|
assert(inst:retainCount() == 3)
|
|
|
|
inst3 = nil --release() on gc
|
|
collectgarbage()
|
|
assert(inst:retainCount() == 2)
|
|
|
|
inst3 = inst:retain()
|
|
assert(inst:retainCount() == 3)
|
|
|
|
inst:release() --manual release()
|
|
assert(inst:retainCount() == 2)
|
|
|
|
inst = nil --object already disowned by inst, refcount should not decrease
|
|
collectgarbage()
|
|
assert(inst2:retainCount() == 2)
|
|
|
|
inst, inst2, inst3 = nil
|
|
collectgarbage()
|
|
end
|
|
|
|
function test.luavars()
|
|
local cls = class(genname(), 'NSObject')
|
|
|
|
--class vars
|
|
cls.myclassvar = 'doh1'
|
|
assert(cls.myclassvar == 'doh1') --intialized
|
|
cls.myclassvar = 'doh'
|
|
assert(cls.myclassvar == 'doh') --updated
|
|
|
|
--inst vars
|
|
local inst = cls:new()
|
|
|
|
inst.myinstvar = 'DOH1'
|
|
assert(inst.myinstvar == 'DOH1') --initialized
|
|
inst.myinstvar = 'DOH'
|
|
assert(inst.myinstvar == 'DOH') --updated
|
|
|
|
--class vars from instances
|
|
assert(inst.myclassvar == 'doh') --class vars are readable from instances
|
|
inst.myclassvar = 'doh2'
|
|
assert(cls.myclassvar == 'doh2') --and they can be updated from instances
|
|
assert(inst.myclassvar == 'doh2')
|
|
|
|
--soft ref counting
|
|
local inst2 = inst:retain()
|
|
assert(inst.myinstvar == 'DOH') --2 refs
|
|
inst = nil
|
|
collectgarbage()
|
|
assert(inst2.myinstvar == 'DOH') --1 ref
|
|
inst2:release() --0 refs; instance gone, vars gone (no way to test, memory was freed)
|
|
assert(cls.myclassvar == 'doh2') --class vars still there
|
|
|
|
local i = 0
|
|
function NSObject:myMethod() i = i + 1 end
|
|
local str = toobj'hello' --create a NSString instance, which is a NSObject
|
|
str:myMethod() --instance method (str passed as self)
|
|
objc.NSString:myMethod() --class method (NSString passed as self)
|
|
assert(i == 2)
|
|
|
|
function NSObject:myMethod() i = i - 1 end --override
|
|
str:myMethod() --instance method (str passed as self)
|
|
objc.NSString:myMethod() --class method (NSString passed as self)
|
|
assert(i == 0)
|
|
end
|
|
|
|
function test.override()
|
|
objc.debug.logtopics.addmethod = true
|
|
|
|
local cls = class(genname(), 'NSObject')
|
|
local metacls = metaclass(cls)
|
|
local obj = cls:new()
|
|
local instdesc = 'hello-instance'
|
|
local classdesc = 'hello-class'
|
|
|
|
function metacls:description() --override the class method
|
|
return classdesc --note: we can return the string directly.
|
|
end
|
|
|
|
function cls:description() --override the instance method
|
|
return instdesc --note: we can return the string directly.
|
|
end
|
|
|
|
assert(objc.tolua(cls:description()) == classdesc) --class method was overriden
|
|
assert(objc.tolua(obj:description()) == instdesc) --instance method was overriden and it's different
|
|
|
|
--subclass and test again
|
|
|
|
local cls2 = class(genname(), cls)
|
|
local metacls2 = metaclass(cls2)
|
|
local obj2 = cls2:new()
|
|
|
|
function metacls2:description() --override the class method
|
|
return objc.callsuper(self, 'description'):UTF8String() .. '2'
|
|
end
|
|
|
|
function cls2:description(callsuper) --override the instance method
|
|
return objc.callsuper(self, 'description'):UTF8String() .. '2'
|
|
end
|
|
|
|
assert(objc.tolua(cls2:description()) == classdesc..'2') --class method was overriden
|
|
assert(objc.tolua(obj2:description()) == instdesc..'2') --instance method was overriden and it's different
|
|
end
|
|
|
|
function test.ivars()
|
|
local obj = NSDocInfo:new()
|
|
|
|
if ffi.abi'64bit' then
|
|
assert(ffi.typeof(obj.time) == ffi.typeof'long long')
|
|
else
|
|
assert(type(obj.time) == 'number')
|
|
end
|
|
assert(type(obj.mode) == 'number') --unsigned short
|
|
assert(ffi.typeof(obj.flags) == ffi.typeof(obj.flags)) --anonymous struct (assert that it was cached)
|
|
|
|
obj.time = 123
|
|
assert(obj.time == 123)
|
|
|
|
assert(obj.flags.isDir == 0)
|
|
obj.flags.isDir = 3 --1 bit
|
|
assert(obj.flags.isDir == 1) --1 bit was set (so this is not a luavar or anything)
|
|
end
|
|
|
|
test.properties = objc.with_properties(function()
|
|
--TODO: find another class with r/w properties. NSProgress is not public on 10.7.
|
|
local pr = NSProgress:progressWithTotalUnitCount(123)
|
|
assert(pr.totalUnitCount == 123) --as initialized
|
|
pr.totalUnitCount = 321 --read/write property
|
|
assert(pr.totalUnitCount == 321)
|
|
assert(not pcall(function() pr.indeterminate = true end)) --attempt to set read-only property
|
|
assert(pr.indeterminate == false)
|
|
end)
|
|
|
|
local timebase, last_time
|
|
function timediff()
|
|
objc.load'System'
|
|
local time
|
|
time = mach_absolute_time()
|
|
if not timebase then
|
|
timebase = ffi.new'mach_timebase_info_data_t'
|
|
mach_timebase_info(timebase)
|
|
end
|
|
local d = tonumber(time - (last_time or 0)) * timebase.numer / timebase.denom / 10^9
|
|
last_time = time
|
|
return d
|
|
end
|
|
|
|
function test.blocks()
|
|
--objc.debug.logtopics.block = true
|
|
local times = 20000
|
|
|
|
timediff()
|
|
|
|
--take 1: creating blocks in inner loops with automatic memory management of blocks.
|
|
local s = NSString:alloc():initWithUTF8String'line1\nline2\nline3'
|
|
for i=1,times do
|
|
local t = {}
|
|
--note: the signature of the block arg for enumerateLinesUsingBlock was taken from bridgesupport.
|
|
s:enumerateLinesUsingBlock(function(line, pstop)
|
|
t[#t+1] = line:UTF8String()
|
|
if #t == 2 then --stop at line 2
|
|
pstop[0] = 1
|
|
end
|
|
end)
|
|
assert(#t == 2)
|
|
assert(t[1] == 'line1')
|
|
assert(t[2] == 'line2')
|
|
--note: callbacks are slow, expensive to create, and limited in number. we have to release them often!
|
|
if i % 200 == 0 then
|
|
collectgarbage()
|
|
end
|
|
end
|
|
|
|
printf('take 1: block in loop (%d times): %4.2fs', times, timediff())
|
|
|
|
--take 2: creating a single block in the outer loop (we must give its type).
|
|
local t
|
|
local blk = toarg(NSString, 'enumerateLinesUsingBlock', 1, function(line, pstop)
|
|
t[#t+1] = line:UTF8String()
|
|
if #t == 2 then --stop at line 2
|
|
pstop[0] = 1
|
|
end
|
|
end)
|
|
local s = NSString:alloc():initWithUTF8String'line1\nline2\nline3'
|
|
for i=1,times do
|
|
t = {}
|
|
s:enumerateLinesUsingBlock(blk)
|
|
assert(#t == 2)
|
|
assert(t[1] == 'line1')
|
|
assert(t[2] == 'line2')
|
|
end
|
|
|
|
printf('take 2: single block (%d times): %4.2fs', times, timediff())
|
|
|
|
end
|
|
|
|
function test.tolua()
|
|
local n = toobj(123.5)
|
|
assert(isa(n, 'NSNumber'))
|
|
assert(tolua(n) == 123.5)
|
|
|
|
local s = toobj'hello'
|
|
assert(isa(s, 'NSString'))
|
|
assert(tolua(s) == 'hello')
|
|
|
|
local a = {1,2,6,7}
|
|
local t = toobj(a)
|
|
assert(t:count() == #a)
|
|
for i=1,#a do
|
|
assert(t:objectAtIndex(i-1):doubleValue() == a[i])
|
|
end
|
|
a = tolua(t)
|
|
assert(#a == 4)
|
|
assert(a[3] == 6)
|
|
|
|
local d = {a = 1, b = 'baz', d = {1,2,3}, [{x=1}] = {y=2}}
|
|
local t = toobj(d)
|
|
assert(t:count() == 4)
|
|
assert(tolua(t:valueForKey(toobj'a')) == d.a)
|
|
assert(tolua(t:valueForKey(toobj'b')) == d.b)
|
|
assert(tolua(t:valueForKey(toobj'd'))[2] == 2)
|
|
end
|
|
|
|
function test.args()
|
|
local s = NSString:alloc():initWithUTF8String'\xE2\x82\xAC' --euro symbol
|
|
--return string
|
|
assert(s:UTF8String() == '\xE2\x82\xAC')
|
|
--return boolean (doesn't work for methods)
|
|
assert(s:isAbsolutePath() == false)
|
|
--return null
|
|
assert(type(s:cStringUsingEncoding(NSASCIIStringEncoding)) == 'nil')
|
|
--selector arg
|
|
assert(s:respondsToSelector'methodForSelector:' == true)
|
|
--class arg
|
|
assert(NSArray:isSubclassOfClass'NSObject' == true)
|
|
assert(NSArray:isSubclassOfClass'XXX' == false)
|
|
--string arg
|
|
assert(NSString:alloc():initWithString('hey'):UTF8String() == 'hey')
|
|
--table arg for array
|
|
local a = NSArray:alloc():initWithArray{6,25,5}
|
|
assert(a:objectAtIndex(1):doubleValue() == 25)
|
|
--table arg for dictionary
|
|
local d = NSDictionary:alloc():initWithDictionary{a=5,b=7}
|
|
assert(d:valueForKey('b'):doubleValue() == 7)
|
|
end
|
|
|
|
function demo.window()
|
|
objc.load'AppKit'
|
|
|
|
local NSApp = class('NSApp', 'NSApplication <NSApplicationDelegate>')
|
|
|
|
--we need to add methods to the class before creating any objects!
|
|
--note: NSApplicationDelegate is an informal protocol brought from bridgesupport.
|
|
|
|
function NSApp:applicationShouldTerminateAfterLastWindowClosed()
|
|
print'last window closed...'
|
|
collectgarbage()
|
|
return true
|
|
end
|
|
|
|
function NSApp:applicationShouldTerminate()
|
|
print'terminating...'
|
|
return true
|
|
end
|
|
|
|
local app = NSApp:sharedApplication()
|
|
app:setDelegate(app)
|
|
app:setActivationPolicy(NSApplicationActivationPolicyRegular)
|
|
|
|
local NSWin = class('NSWin', 'NSWindow <NSWindowDelegate>')
|
|
|
|
--we need to add methods to the class before creating any objects!
|
|
--note: NSWindowDelegate is a formal protocol brought from the runtime.
|
|
|
|
function NSWin:windowWillClose()
|
|
print'window will close...'
|
|
end
|
|
|
|
local style = bit.bor(
|
|
NSTitledWindowMask,
|
|
NSClosableWindowMask,
|
|
NSMiniaturizableWindowMask,
|
|
NSResizableWindowMask)
|
|
|
|
local win = NSWin:alloc():initWithContentRect_styleMask_backing_defer(
|
|
NSMakeRect(300, 300, 500, 300), style, NSBackingStoreBuffered, false)
|
|
win:setDelegate(win)
|
|
win:setTitle"▀▄▀▄▀▄ [ Lua Rulez ] ▄▀▄▀▄▀"
|
|
|
|
app:activateIgnoringOtherApps(true)
|
|
win:makeKeyAndOrderFront(nil)
|
|
|
|
app:run()
|
|
end
|
|
|
|
function demo.speech()
|
|
objc.load'AppKit'
|
|
local speech = NSSpeechSynthesizer:new()
|
|
voiceid = NSSpeechSynthesizer:availableVoices():objectAtIndex(11)
|
|
speech:setVoice(voiceid)
|
|
speech:startSpeakingString'Calm, fitter, healthier, and more productive; A pig. In a cage. On antibiotics.'
|
|
while speech:isSpeaking() do
|
|
os.execute'sleep 1'
|
|
end
|
|
end
|
|
|
|
function demo.http() --what a dense word soup just to make a http request
|
|
objc.load'AppKit'
|
|
local app = NSApplication:sharedApplication()
|
|
|
|
local post = NSString:stringWithFormat('firstName=%@&lastName=%@&eMail=%@&message=%@',
|
|
toobj'Dude', toobj'Edud', toobj'x@y.com', toobj'message')
|
|
local postData = post:dataUsingEncoding(NSUTF8StringEncoding)
|
|
local postLength = NSString:stringWithFormat('%ld', postData:length())
|
|
NSLog('Post data: %@', post)
|
|
local request = NSMutableURLRequest:new()
|
|
request:setURL(NSURL:URLWithString'http://posttestserver.com/post.php')
|
|
request:setHTTPMethod'POST'
|
|
request:setValue_forHTTPHeaderField(postLength, 'Content-Length')
|
|
request:setValue_forHTTPHeaderField('application/x-www-form-urlencoded', 'Content-Type')
|
|
request:setHTTPBody(postData)
|
|
|
|
NSLog('%@', request)
|
|
|
|
local CD = class('ConnDelegate', 'NSObject <NSURLConnectionDelegate, NSURLConnectionDataDelegate>')
|
|
|
|
function CD:connection_didReceiveData(conn, data)
|
|
self.webData:appendData(data)
|
|
NSLog'Connection received data'
|
|
end
|
|
|
|
function CD:connection_didReceiveResponse(conn, response)
|
|
NSLog'Connection received response'
|
|
NSLog('%@', response:description())
|
|
end
|
|
|
|
function CD:connection_didFailWithError(conn, err)
|
|
NSLog('Connection error: %@', err:localizedDescription())
|
|
app:terminate(nil)
|
|
end
|
|
|
|
function CD:connectionDidFinishLoading(conn)
|
|
NSLog'Connection finished loading'
|
|
local html = NSString:alloc():initWithBytes_length_encoding(self.webData:mutableBytes(),
|
|
self.webData:length(), NSUTF8StringEncoding)
|
|
NSLog('OUTPUT:\n%@', html)
|
|
app:terminate(nil)
|
|
end
|
|
|
|
local cd = ConnDelegate:new()
|
|
cd.webData = NSMutableData:new()
|
|
|
|
local conn = NSURLConnection:alloc():initWithRequest_delegate_startImmediately(request, cd, false)
|
|
conn:start()
|
|
|
|
app:run()
|
|
end
|
|
|
|
function demo.http_gcd()
|
|
objc.load'AppKit'
|
|
local app = NSApplication:sharedApplication()
|
|
|
|
local url = NSURL:URLWithString'http://posttestserver.com/post.php'
|
|
local req = NSURLRequest:requestWithURL(url)
|
|
|
|
local queue = dispatch.main_queue --dispatch.get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
|
|
|
objc.debug.logtopics.block = true
|
|
|
|
local n = 0
|
|
local blk = block(function()
|
|
n = n + 1
|
|
print('called', n)
|
|
|
|
local response = ffi.new'id[1]'
|
|
local err = ffi.new'id[1]'
|
|
local data = NSURLConnection:sendSynchronousRequest_returningResponse_error(req, response, err)
|
|
|
|
print(tolua(NSString:alloc():initWithBytes_length_encoding(
|
|
data:mutableBytes(), data:length(), NSUTF8StringEncoding)))
|
|
|
|
if n == 2 then
|
|
print'---- Done. Hit Ctrl+C twice ----'
|
|
end
|
|
end)
|
|
dispatch.async(queue, blk) --increase refcount
|
|
dispatch.async(queue, blk) --increase refcount
|
|
print'queued'
|
|
blk = nil; collectgarbage() --decrease refcount (stil queued)
|
|
print'released'
|
|
app:run()
|
|
end
|
|
|
|
-- inspection ------------------------------------------------------------------------------------------------------------
|
|
|
|
local function load_many_frameworks()
|
|
objc.debug.loaddeps = true
|
|
for s in string.gmatch([[
|
|
AGL
|
|
AVFoundation
|
|
AVKit
|
|
Accelerate
|
|
Accounts
|
|
AddressBook
|
|
AppKit
|
|
AppKitScripting
|
|
AppleScriptKit
|
|
AppleScriptObjC
|
|
AppleShareClientCore
|
|
ApplicationServices
|
|
AudioToolbox
|
|
AudioUnit
|
|
AudioVideoBridging
|
|
Automator
|
|
CFNetwork
|
|
CalendarStore
|
|
Carbon
|
|
Cocoa
|
|
Collaboration
|
|
CoreAudio
|
|
CoreAudioKit
|
|
CoreData
|
|
CoreFoundation
|
|
CoreGraphics
|
|
CoreLocation
|
|
CoreMIDI
|
|
CoreMedia
|
|
CoreMediaIO
|
|
CoreServices
|
|
CoreText
|
|
CoreVideo
|
|
CoreWLAN
|
|
DVComponentGlue
|
|
DVDPlayback
|
|
DirectoryService
|
|
DiscRecording
|
|
DiscRecordingUI
|
|
DiskArbitration
|
|
DrawSprocket
|
|
EventKit
|
|
ExceptionHandling
|
|
FWAUserLib
|
|
ForceFeedback
|
|
Foundation
|
|
GLKit
|
|
GLUT
|
|
GSS
|
|
GameController
|
|
GameKit
|
|
ICADevices
|
|
IMServicePlugIn
|
|
IOBluetooth
|
|
IOBluetoothUI
|
|
IOKit
|
|
IOSurface
|
|
ImageCaptureCore
|
|
ImageIO
|
|
InputMethodKit
|
|
InstallerPlugins
|
|
InstantMessage
|
|
JavaFrameEmbedding
|
|
JavaScriptCore
|
|
Kerberos
|
|
LDAP
|
|
LatentSemanticMapping
|
|
MapKit
|
|
MediaAccessibility
|
|
MediaLibrary
|
|
MediaToolbox
|
|
NetFS
|
|
OSAKit
|
|
OpenAL
|
|
OpenCL
|
|
OpenDirectory
|
|
OpenGL
|
|
PCSC
|
|
PreferencePanes
|
|
PubSub
|
|
QTKit
|
|
Quartz
|
|
QuartzCore
|
|
QuickLook
|
|
SceneKit
|
|
ScreenSaver
|
|
Scripting
|
|
ScriptingBridge
|
|
Security
|
|
SecurityFoundation
|
|
SecurityInterface
|
|
ServiceManagement
|
|
Social
|
|
SpriteKit
|
|
StoreKit
|
|
SyncServices
|
|
System
|
|
SystemConfiguration
|
|
TWAIN
|
|
Tcl
|
|
Tk
|
|
VideoDecodeAcceleration
|
|
VideoToolbox
|
|
WebKit
|
|
]], '([^\n\r]+)') do
|
|
pcall(objc.load, s)
|
|
end
|
|
objc.debug.loaddeps = false
|
|
end
|
|
|
|
function eyetest.inspect_classes()
|
|
load_many_frameworks()
|
|
inspect.classes()
|
|
end
|
|
|
|
function eyetest.inspect_protocols()
|
|
load_many_frameworks()
|
|
inspect.protocols()
|
|
end
|
|
|
|
function eyetest.inspect_class_properties(cls)
|
|
load_many_frameworks()
|
|
inspect.class_properties(cls)
|
|
end
|
|
|
|
function eyetest.inspect_protocol_properties(proto)
|
|
load_many_frameworks()
|
|
inspect.protocol_properties(proto)
|
|
end
|
|
|
|
local function req(s)
|
|
return s and s ~= '' and s or nil
|
|
end
|
|
|
|
function eyetest.inspect_class_methods(cls, inst)
|
|
load_many_frameworks()
|
|
inspect.class_methods(req(cls), inst == 'inst')
|
|
end
|
|
|
|
function eyetest.inspect_protocol_methods(proto, inst, required)
|
|
load_many_frameworks()
|
|
inspect.protocol_methods(req(proto), inst == 'inst', required == 'required')
|
|
end
|
|
|
|
function eyetest.inspect_class_ivars(cls)
|
|
load_many_frameworks()
|
|
inspect.class_ivars(req(cls))
|
|
end
|
|
|
|
function eyetest.inspect_class(cls)
|
|
load_many_frameworks()
|
|
inspect.class(cls)
|
|
end
|
|
|
|
function eyetest.inspect_protocol(proto)
|
|
load_many_frameworks()
|
|
inspect.protocol(proto)
|
|
end
|
|
|
|
function eyetest.inspect_find(patt)
|
|
load_many_frameworks()
|
|
inspect.find(patt)
|
|
end
|
|
|
|
--------------
|
|
|
|
local function test_all(tests, ...)
|
|
for k,v in glue.sortedpairs(tests) do
|
|
if k ~= 'all' then
|
|
print(k)
|
|
hr()
|
|
tests[k](...)
|
|
end
|
|
end
|
|
end
|
|
|
|
function test.all(...)
|
|
test_all(test)
|
|
end
|
|
|
|
--cmdline interface
|
|
|
|
local function run(...)
|
|
local testname = ...
|
|
if not testname then
|
|
print('Usage: '..luajit..' '..arg[0]..' <test>')
|
|
for k,t in glue.sortedpairs(tests) do
|
|
printf('%s:', k)
|
|
for k in glue.sortedpairs(t) do
|
|
print('', k)
|
|
end
|
|
end
|
|
else
|
|
local test = test[testname] or eyetest[testname] or demo[testname]
|
|
if not test then
|
|
printf('Invalid test "%s"', tostring(testname))
|
|
os.exit(1)
|
|
end
|
|
test(select(2, ...))
|
|
print'ok'
|
|
end
|
|
end
|
|
|
|
run(...)
|