This repository has been archived by the owner on Jul 22, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 80
/
Copy pathgtype.jl
505 lines (464 loc) · 19.1 KB
/
gtype.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
abstract type GObject end
abstract type GInterface <: GObject end
abstract type GBoxed end
mutable struct GBoxedUnkown <: GBoxed
handle::Ptr{GBoxed}
end
const GEnum = Int32
const GType = Csize_t
struct GParamSpec
g_type_instance::Ptr{Nothing}
name::Ptr{UInt8}
flags::Cint
value_type::GType
owner_type::GType
end
const fundamental_types = (
#(:name, Ctype, JuliaType, g_value_fn)
(:invalid, Nothing, Union{}, :error),
(:void, Nothing, Nothing, :error),
(:GInterface, Ptr{Nothing}, GInterface, :error),
(:gchar, Int8, Int8, :schar),
(:guchar, UInt8, UInt8, :uchar),
(:gboolean, Cint, Bool, :boolean),
(:gint, Cint, Union{}, :int),
(:guint, Cuint, Union{}, :uint),
(:glong, Clong, Union{}, :long),
(:gulong, Culong, Union{}, :ulong),
(:gint64, Int64, Signed, :int64),
(:guint64, UInt64, Unsigned, :uint64),
(:GEnum, GEnum, Union{}, :enum),
(:GFlags, GEnum, Union{}, :flags),
(:gfloat, Float32, Float32, :float),
(:gdouble, Float64, AbstractFloat, :double),
(:gchararray, Ptr{UInt8}, AbstractString, :string),
(:gpointer, Ptr{Nothing}, Ptr, :pointer),
(:GBoxed, Ptr{GBoxed}, GBoxed, :boxed),
(:GParam, Ptr{GParamSpec}, Ptr{GParamSpec},:param),
(:GObject, Ptr{GObject}, GObject, :object),
#(:GVariant, Ptr{GVariant}, GVariant, :variant),
)
# NOTE: in general do not cache ids, except for these fundamental values
g_type_from_name(name::Symbol) = ccall((:g_type_from_name, libgobject), GType, (Ptr{UInt8},), name)
const fundamental_ids = tuple(GType[g_type_from_name(name) for (name, c, j, f) in fundamental_types]...)
g_type(gtyp::GType) = gtyp
let jtypes = Expr(:block, :( g_type(::Type{Nothing}) = $(g_type_from_name(:void)) ))
for i = 1:length(fundamental_types)
(name, ctype, juliatype, g_value_fn) = fundamental_types[i]
if juliatype != Union{}
push!(jtypes.args, :( g_type(::Type{T}) where {T <: $juliatype} = convert(GType, $(fundamental_ids[i])) ))
end
end
Core.eval(GLib, jtypes)
end
G_TYPE_FROM_CLASS(w::Ptr{Nothing}) = unsafe_load(convert(Ptr{GType}, w))
G_OBJECT_GET_CLASS(w::GObject) = G_OBJECT_GET_CLASS(w.handle)
G_OBJECT_GET_CLASS(hnd::Ptr{GObject}) = unsafe_load(convert(Ptr{Ptr{Nothing}}, hnd))
G_OBJECT_CLASS_TYPE(w) = G_TYPE_FROM_CLASS(G_OBJECT_GET_CLASS(w))
g_isa(gtyp::GType, is_a_type::GType) = ccall((:g_type_is_a, libgobject), Cint, (GType, GType), gtyp, is_a_type) != 0
g_isa(gtyp, is_a_type) = g_isa(g_type(gtyp), g_type(is_a_type))
g_type_parent(child::GType) = ccall((:g_type_parent, libgobject), GType, (GType,), child)
g_type_name(g_type::GType) = Symbol(bytestring(ccall((:g_type_name, libgobject), Ptr{UInt8}, (GType,), g_type)))
g_type_test_flags(g_type::GType, flag) = ccall((:g_type_test_flags, libgobject), Bool, (GType, GEnum), g_type, flag)
const G_TYPE_FLAG_CLASSED = 1 << 0
const G_TYPE_FLAG_INSTANTIATABLE = 1 << 1
const G_TYPE_FLAG_DERIVABLE = 1 << 2
const G_TYPE_FLAG_DEEP_DERIVABLE = 1 << 3
mutable struct GObjectLeaf <: GObject
handle::Ptr{GObject}
function GObjectLeaf(handle::Ptr{GObject})
if handle == C_NULL
error("Cannot construct $gname with a NULL pointer")
end
return gobject_ref(new(handle))
end
end
g_type(obj::GObject) = g_type(typeof(obj))
gtypes(types...) = GType[g_type(t) for t in types]
const gtype_abstracts = Dict{Symbol, Type}()
const gtype_wrappers = Dict{Symbol, Type}()
const gtype_ifaces = Dict{Symbol, Type}()
gtype_abstracts[:GObject] = GObject
gtype_wrappers[:GObject] = GObjectLeaf
let libs = Dict{AbstractString, Any}()
global get_fn_ptr
function get_fn_ptr(fnname, lib, cm)
if !isa(lib, AbstractString)
lib = Core.eval(cm, lib)
end
libptr = get(libs, lib, C_NULL)::Ptr{Nothing}
if libptr == C_NULL
libs[lib] = libptr = dlopen(lib)
end
fnptr = dlsym_e(libptr, fnname)
end
end
function g_type(name::Symbol, lib, symname::Symbol, cm)
if name in keys(gtype_wrappers)
return g_type(gtype_wrappers[name])
end
fnptr = get_fn_ptr(string(symname, "_get_type"), lib, cm)
if fnptr != C_NULL
ccall(fnptr, GType, ())
else
convert(GType, 0)
end
end
g_type(name::Symbol, lib, symname::Expr, cm) = Core.eval(cm, symname)
g_type(name::Expr, lib::Expr, symname::Expr, cm) = info( (name,lib,symname) )
function get_interface_decl(iname::Symbol, gtyp::GType, gtyp_decl, cm)
if isdefined(cm, iname)
return nothing
end
parent = g_type_parent(gtyp)
@assert parent != 0
piname = g_type_name(parent)
quote
if $(QuoteNode(iname)) in keys(gtype_ifaces)
$(esc(iname)) = gtype_abstracts[$(Meta.quot(iname))]
else
struct $(esc(iname)) <: GInterface
handle::Ptr{GObject}
gc::Any
$(esc(iname))(x::GObject) = new(unsafe_convert(Ptr{GObject}, x), x)
# Gtk does an interface type check when calling methods. So, it's
# not worth repeating it here. Plus, we might as well just allow
# the user to lie, since we aren't using this for dispatch
# (like C & unlike most other languages), the user may be able
# to write more generic code
end
gtype_ifaces[$(QuoteNode(iname))] = $(esc(iname))
local T #to prevent Julia-0.2 from name-mangling <: T
$gtyp_decl
end
nothing
end
end
function get_itype_decl(iname::Symbol, gtyp::GType, cm)
if isdefined(cm, iname)
return nothing
end
if iname === :GObject
return :( $(esc(iname)) = gtype_abstracts[:GObject] )
end
#ntypes = mutable(Cuint)
#interfaces = ccall((:g_type_interfaces, libgobject), Ptr{GType}, (GType, Ptr{Cuint}), gtyp, ntypes)
#for i = 1:ntypes[]
# interface = unsafe_load(interfaces, i)
# # what do we care to do here?!
#end
#g_free(interfaces)
parent = g_type_parent(gtyp)
@assert parent != 0
piname = g_type_name(parent)
piface_decl = get_itype_decl(piname, parent, cm)
quote
if $(QuoteNode(iname)) in keys(gtype_abstracts)
$(esc(iname)) = gtype_abstracts[$(QuoteNode(iname))]
else
$piface_decl
abstract type $(esc(iname)) <: $(esc(piname)) end
gtype_abstracts[$(QuoteNode(iname))] = $(esc(iname))
end
nothing
end
end
get_gtype_decl(name::Symbol, lib, symname::Expr) =
:( GLib.g_type(::Type{T}) where {T <: $(esc(name))} = $(esc(symname)) )
let handled = Set()
global get_gtype_decl
function get_gtype_decl(name::Symbol, lib, symname::Symbol)
if !(name in handled)
push!(handled, name)
return :( GLib.g_type(::Type{T}) where {T <: $(esc(name))} =
ccall(($(QuoteNode(Symbol(string(symname, "_get_type")))), $(esc(lib))), GType, ()) )
end
nothing
end
end #let
function get_type_decl(name, iname, gtyp, gtype_decl, cm)
ename = esc(name)
einame = esc(iname)
quote
if $(QuoteNode(iname)) in keys(gtype_wrappers)
$einame = gtype_abstracts[$(QuoteNode(iname))]
else
$(get_itype_decl(iname, gtyp, cm))
end
mutable struct $ename <: $einame
handle::Ptr{GObject}
function $ename(handle::Ptr{GObject})
if handle == C_NULL
error($("Cannot construct $name with a NULL pointer"))
end
return gobject_ref(new(handle))
end
end
local kwargs, T #to prevent Julia-0.2 from name-mangling kwargs, <: T
function $ename(args...; kwargs...)
if isempty(kwargs)
error(MethodError($ename, args))
end
w = $ename(args...)
for (kw, val) in kwargs
set_gtk_property!(w, kw, val)
end
w
end
gtype_wrappers[$(QuoteNode(iname))] = $ename
function $einame(args...; kwargs...)
$ename(args...; kwargs...)
end
$gtype_decl
nothing
end
end
macro Gtype_decl(name, gtyp, gtype_decl)
get_type_decl(name, Symbol(string(name, __module__.suffix)), gtyp, gtype_decl, __module__)
end
macro Gtype(iname, lib, symname)
gtyp = g_type(iname, lib, symname, __module__)
if gtyp == 0
return Expr(:call, :error, string("Could not find ", symname, " in ", lib,
". This is likely a issue with a missing Gtk.jl version check."))
end
@assert iname === g_type_name(gtyp)
if !g_type_test_flags(gtyp, G_TYPE_FLAG_CLASSED)
error("GType is currently only implemented for G_TYPE_FLAG_CLASSED")
end
gtype_decl = get_gtype_decl(iname, lib, symname)
name = Symbol(string(iname, __module__.suffix))
get_type_decl(name, iname, gtyp, gtype_decl, __module__)
end
macro Gabstract(iname, lib, symname)
gtyp = g_type(iname, lib, symname)
if gtyp == 0
return Expr(:call, :error, string("Could not find ", symname, " in ", lib, ". This is likely a issue with a missing Gtk.jl version check."))
end
@assert iname === g_type_name(gtyp)
Expr(:block,
get_itype_decl(iname, gtyp, __module__),
get_gtype_decl(iname, lib, symname))
end
macro Giface(iname, lib, symname)
gtyp = g_type(iname, lib, symname, __module__)
if gtyp == 0
return Expr(:call, :error, string("Could not find ", symname, " in ", lib, ". This is likely a issue with a missing Gtk.jl version check."))
end
@assert iname === g_type_name(gtyp)
gtype_decl = get_gtype_decl(iname, lib, symname)
get_interface_decl(iname::Symbol, gtyp::GType, gtype_decl, __module__)
end
macro quark_str(q)
:( ccall((:g_quark_from_string, libglib), UInt32, (Ptr{UInt8},), bytestring($q)) )
end
unsafe_convert(::Type{Ptr{T}}, box::T) where {T <: GBoxed} = convert(Ptr{T}, box.handle)
convert(::Type{GBoxed}, boxed::GBoxed) = boxed
convert(::Type{GBoxedUnkown}, boxed::GBoxedUnkown) = boxed
convert(::Type{T}, boxed::T) where {T <: GBoxed} = boxed
convert(::Type{T}, boxed::GBoxed) where {T <: GBoxed} = convert(T, boxed.handle)
convert(::Type{GBoxed}, unbox::Ptr{GBoxed}) = GBoxedUnkown(unbox)
convert(::Type{GBoxed}, unbox::Ptr{T}) where {T <: GBoxed} = GBoxedUnkown(unbox)
convert(::Type{T}, unbox::Ptr{GBoxed}) where {T <: GBoxed} = convert(T, convert(Ptr{T}, unbox))
convert(::Type{T}, unbox::Ptr{T}) where {T <: GBoxed} = T(unbox)
cconvert(::Type{Ptr{GObject}}, @nospecialize(x::GObject)) = x
# All GObjects are expected to have a 'handle' field
# of type Ptr{GObject} corresponding to the GLib object
# or to override this method (e.g. GtkNullContainer, AbstractString)
unsafe_convert(::Type{Ptr{GObject}}, w::GObject) = getfield(w, :handle)
# this method should be used by gtk methods returning widgets of unknown type
# and/or that might have been wrapped by julia before,
# instead of a direct call to the constructor
convert(::Type{T}, w::Ptr{GObject}) where {T <: GObject} = convert_(T, convert(Ptr{T}, w)) # this definition must be first due to a 0.2 dispatch bug
convert(::Type{T}, ptr::Ptr{T}) where T <: GObject = convert_(T, ptr)
# need to introduce convert_ since otherwise there was a StackOverFlow error
function convert_(::Type{T}, ptr::Ptr{T}) where T <: GObject
hnd = convert(Ptr{GObject}, ptr)
if hnd == C_NULL
throw(UndefRefError())
end
x = ccall((:g_object_get_qdata, libgobject), Ptr{GObject}, (Ptr{GObject}, UInt32), hnd, jlref_quark::UInt32)
if x != C_NULL
return gobject_ref(unsafe_pointer_to_objref(x)::T)
end
wrap_gobject(hnd)::T
end
function wrap_gobject(hnd::Ptr{GObject})
gtyp = G_OBJECT_CLASS_TYPE(hnd)
typname = g_type_name(gtyp)
while !(typname in keys(gtype_wrappers))
gtyp = g_type_parent(gtyp)
@assert gtyp != 0
typname = g_type_name(gtyp)
end
T = gtype_wrappers[typname]
return T(hnd)
end
eltype(::Type{_LList{T}}) where {T <: GObject} = T
ref_to(::Type{T}, x) where {T <: GObject} = gobject_ref(unsafe_convert(Ptr{GObject}, x))
deref_to(::Type{T}, x::Ptr) where {T <: GObject} = convert(T, x)
empty!(li::Ptr{_LList{Ptr{T}}}) where {T <: GObject} = gc_unref(unsafe_load(li).data)
### Miscellaneous types
baremodule GConnectFlags
const AFTER = 1
const SWAPPED = 2
get(s::Symbol) =
if s === :after
AFTER
elseif s === :swapped
SWAPPED
else
Main.Base.error(Main.Base.string("invalid GConnectFlag ", s))
end
end
### Garbage collection [prevention]
const gc_preserve = IdDict{Any, Any}() # reference counted closures
function gc_ref(@nospecialize(x))
global gc_preserve
local ref::Ref{Any}, cnt::Int
if x in keys(gc_preserve)
ref, cnt = gc_preserve[x]::Tuple{Ref{Any}, Int}
else
ref = Ref{Any}(x)
cnt = 0
end
gc_preserve[x] = (ref, cnt + 1)
return unsafe_load(convert(Ptr{Ptr{Nothing}}, unsafe_convert(Ptr{Any}, ref)))
end
function gc_unref(@nospecialize(x))
global gc_preserve
ref, cnt = gc_preserve[x]::Tuple{Ref{Any}, Int}
@assert cnt > 0
if cnt == 1
delete!(gc_preserve, x)
else
gc_preserve[x] = (ref, cnt - 1)
end
nothing
end
_gc_unref(@nospecialize(x), ::Ptr{Nothing}) = gc_unref(x)
gc_ref_closure(@nospecialize(cb::Function)) = (invoke(gc_ref, Tuple{Any}, cb), @cfunction(_gc_unref, Nothing, (Any, Ptr{Nothing})))
gc_ref_closure(x::T) where {T} = (gc_ref(x), @cfunction(_gc_unref, Nothing, (Any, Ptr{Nothing})))
# generally, you shouldn't be calling gc_ref(::Ptr{GObject})
gc_ref(x::Ptr{GObject}) = ccall((:g_object_ref, libgobject), Nothing, (Ptr{GObject},), x)
gc_unref(x::Ptr{GObject}) = ccall((:g_object_unref, libgobject), Nothing, (Ptr{GObject},), x)
const gc_preserve_glib = Dict{Union{WeakRef, GObject}, Bool}() # glib objects
const gc_preserve_glib_lock = Ref(false) # to satisfy this lock, must never decrement a ref counter while it is held
const topfinalizer = Ref(true) # keep recursion to a minimum by only iterating from the top
const await_finalize = Set{Any}()
Base.isequal(x::GObject, w::WeakRef) = x === w.value # cuts the number of MethodInstances from O(N^2) to O(N)
function finalize_gc_unref(@nospecialize(x::GObject))
# this records that the are no user references left to the object from Julia
# and notifies GLib that it can free the object (if no reference exist from C)
# it is intended to be called by GC, not in user code function
istop = topfinalizer[]
topfinalizer[] = false
gc_preserve_glib_lock[] = true
delete!(gc_preserve_glib, x)
if getfield(x, :handle) != C_NULL
gc_preserve_glib[x] = true # convert to a strong-reference
gc_preserve_glib_lock[] = false
gc_unref(unsafe_convert(Ptr{GObject}, x)) # may clear the strong reference
else
gc_preserve_glib_lock[] = false
end
topfinalizer[] = istop
istop && run_delayed_finalizers()
nothing
end
function delref(@nospecialize(x::GObject))
# internal helper function
exiting[] && return # unnecessary to cleanup if we are about to die anyways
if gc_preserve_glib_lock[] || g_yielded[]
push!(await_finalize, x)
return # avoid running finalizers at random times
end
finalize_gc_unref(x)
nothing
end
function addref(@nospecialize(x::GObject))
# internal helper function
ccall((:g_object_ref_sink, libgobject), Ptr{GObject}, (Ptr{GObject},), x)
finalizer(delref, x)
delete!(gc_preserve_glib, x) # in v0.2, the WeakRef assignment below wouldn't update the key
gc_preserve_glib[WeakRef(x)] = false # record the existence of the object, but allow the finalizer
nothing
end
function gobject_ref(x::T) where T <: GObject
gc_preserve_glib_lock[] = true
strong = get(gc_preserve_glib, x, nothing)
if strong === nothing
if ccall((:g_object_get_qdata, libgobject), Ptr{Cvoid},
(Ptr{GObject}, UInt32), x, jlref_quark::UInt32) != C_NULL
# have set up metadata for this before, but its weakref has been cleared. restore the ref.
delete!(await_finalize, x)
finalizer(delref, x)
gc_preserve_glib[WeakRef(x)] = false # record the existence of the object, but allow the finalizer
else
# we haven't seen this before, setup the metadata
deref = @cfunction(gc_unref, Nothing, (Ref{T},))
ccall((:g_object_set_qdata_full, libgobject), Nothing,
(Ptr{GObject}, UInt32, Any, Ptr{Nothing}), x, jlref_quark::UInt32, x,
deref) # add a circular reference to the Julia object in the GObject
addref(Ref{GObject}(x)[])
end
elseif strong
# oops, we previously deleted the link, but now it's back
addref(Ref{GObject}(x)[])
else
# already gc-protected, nothing to do
end
gc_preserve_glib_lock[] = false
run_delayed_finalizers()
return x
end
gc_ref(x::GObject) = pointer_from_objref(gobject_ref(x))
function run_delayed_finalizers()
exiting[] && return # unnecessary to cleanup if we are about to die anyways
g_yielded[] && return # can't run them right now
topfinalizer[] = false
while !isempty(await_finalize)
x = pop!(await_finalize)
finalize_gc_unref(x)
end
topfinalizer[] = true
end
function gc_unref_weak(x::GObject)
# this strongly destroys and invalidates the object
# it is intended to be called by GLib, not in user code function
# note: this may be called multiple times by GLib
x.handle = C_NULL
gc_preserve_glib_lock[] = true
delete!(gc_preserve_glib, x)
gc_preserve_glib_lock[] = false
nothing
end
function gc_unref(x::GObject)
# this strongly destroys and invalidates the object
# it is intended to be called by GLib, not in user code function
ref = ccall((:g_object_get_qdata, libgobject), Ptr{Nothing}, (Ptr{GObject}, UInt32), x, jlref_quark::UInt32)
if ref != C_NULL && x !== unsafe_pointer_to_objref(ref)
# We got called because we are no longer the default object for this handle, but we are still alive
@warn("Duplicate Julia object creation detected for GObject")
deref = cfunction_(gc_unref_weak, Nothing, (Ref{typeof(x)},))
ccall((:g_object_weak_ref, libgobject), Nothing, (Ptr{GObject}, Ptr{Nothing}, Any), x, deref, x)
else
ccall((:g_object_steal_qdata, libgobject), Any, (Ptr{GObject}, UInt32), x, jlref_quark::UInt32)
gc_unref_weak(x)
end
nothing
end
gc_unref(::Ptr{GObject}, x::GObject) = gc_unref(x)
gc_ref_closure(x::GObject) = (gc_ref(x), C_NULL)
function gc_force_floating(x::GObject)
ccall((:g_object_force_floating, libgobject), Nothing, (Ptr{GObject},), x)
end
function gobject_move_ref(new::GObject, old::GObject)
h = unsafe_convert(Ptr{GObject}, new)
@assert h == unsafe_convert(Ptr{GObject}, old) != C_NULL
gc_ref(h)
gc_unref(old)
gc_ref(new)
gc_unref(h)
new
end