Skip to content

Commit

Permalink
Rework event loop structure, stop using exceptions for termination
Browse files Browse the repository at this point in the history
  • Loading branch information
serenity4 committed Jun 10, 2022
1 parent 87670f7 commit 66bc85a
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 63 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/Test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
julia-version: ['1.5', 'nightly']
julia-version: ['1.7', 'nightly']
julia-arch: [x64, x86]
os: [ubuntu-latest]

Expand Down
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Changelog for XCB.jl

## Version `v0.5`

- ![BREAKING][badge-breaking] ![Enhancement][badge-enhancement] `Base.run(::XWindowManager; kwargs...)` now only accepts a single keyword argument `sleep_time`, removing the ability to customize `execute_callback` and removing `poll` such that by default a mix of polling and (optionally) sleeping is done according to `sleep_time` (instead of either polling or calling a blocking C function). `iter_first` and `iter_last` keyword arguments are no longer available, and you should make your own event loop with `poll_event` and `process_event` if you want further customization.
- ![BREAKING][badge-breaking] ![Enhancement][badge-enhancement] Closing a window is now done by returning a `CloseWindow` instance instead of throwing it as an exception. Note that it is still an `Exception`, so you can easily reproduce the old behavior with a manual `try`/`catch` block.
- ![BREAKING][badge-breaking] ![Enhancement][badge-enhancement] The time field of `EventDetails` is now filled with the time since epoch, and not since the window has been created. You can always get the time since window creation yourself by recording `t0 = time()` when creating the window and subtracting it if desired.

[badge-breaking]: https://img.shields.io/badge/BREAKING-red.svg
[badge-deprecation]: https://img.shields.io/badge/deprecation-orange.svg
[badge-feature]: https://img.shields.io/badge/feature-green.svg
[badge-enhancement]: https://img.shields.io/badge/enhancement-blue.svg
[badge-bugfix]: https://img.shields.io/badge/bugfix-purple.svg
[badge-security]: https://img.shields.io/badge/security-black.svg
[badge-experimental]: https://img.shields.io/badge/experimental-lightgrey.svg
[badge-maintenance]: https://img.shields.io/badge/maintenance-gray.svg

<!--
# Badges (reused from the CHANGELOG.md of Documenter.jl)
![BREAKING][badge-breaking]
![Deprecation][badge-deprecation]
![Feature][badge-feature]
![Enhancement][badge-enhancement]
![Bugfix][badge-bugfix]
![Security][badge-security]
![Experimental][badge-experimental]
![Maintenance][badge-maintenance]
-->
6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "XCB"
uuid = "16167f82-ea26-5cba-b1de-ed6fd5e30a10"
version = "0.4.1"
version = "0.5.0"

[deps]
CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82"
Expand All @@ -17,10 +17,10 @@ CEnum = "0.4"
DocStringExtensions = "0.8"
Reexport = "1"
UnPack = "1"
WindowAbstractions = "0.4"
WindowAbstractions = "0.5"
Xorg_libxcb_jll = "1.13"
Xorg_xcb_util_jll = "0.4"
julia = "1.5"
julia = "1.7"
xkbcommon_jll = "0.9"

[extras]
Expand Down
2 changes: 1 addition & 1 deletion src/XCB.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import WindowAbstractions: set_title,
unmap_window,
wait_for_event,
poll_for_event,
handle_event,
attach_graphics_context!,
get_window,
get_window_symbol,
Expand Down Expand Up @@ -67,7 +68,6 @@ export xcb,
Setup,
check,
check_flush,
flush,
current_screen,
XCBWindow,
GraphicsContext,
Expand Down
49 changes: 5 additions & 44 deletions src/events.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ response_type(data) = Int(data.response_type & 0x7f)

is_from_send_request(data) = Bool(data.response_type & 0x80)

function unsafe_load_event(xge_ptr; warn_unknown=false)
function unsafe_load_event(xge_ptr)
xge = unsafe_load(xge_ptr)
rt = response_type(xge)
et = event_type(Val(rt))
if isnothing(et)
warn_unknown && @warn "Unknown event $(rt)"
@debug "Unknown event $(rt)"
nothing
else
unsafe_load(convert(Ptr{et}, xge_ptr))
Expand Down Expand Up @@ -64,15 +64,13 @@ EventDetails(wm::XWindowManager, win::XCBWindow, data::xcb_expose_event_t, t) =
EventDetails(wm::XWindowManager, win::XCBWindow, data::xcb_configure_notify_event_t, t) =
EventDetails(win, ResizeEvent((data.width, data.height)), data, t)

process_xevent(wm::XWindowManager, xge::Nothing, t) = nothing

function process_xevent(wm::XWindowManager, event, t)
function handle_event(wm::XWindowManager, event)
if !isnothing(event) # event is known
win = get_window(wm, event)
if event isa xcb_client_message_event_t
ed_8 = Int.(event.data.data8)
event_data32_1 = ed_8[1] + ed_8[2] * 2^8 + ed_8[3] * 2^16 + ed_8[4] * 2^24
event_data32_1 == win.delete_request && throw(CloseWindow(win, ""))
event_data32_1 == win.delete_request && return CloseWindow(win, "")
nothing
elseif event isa xcb_xkb_state_notify_event_t
xkb_state_update_mask(wm.keymap.state, event.baseMods, event.latchedMods, event.lockedMods, event.baseGroup, event.latchedGroup, event.lockedGroup)
Expand All @@ -81,48 +79,11 @@ function process_xevent(wm::XWindowManager, event, t)
wm.keymap = Keymap(wm.conn; setup_xkb=false)
nothing
elseif !isnothing(win) # happened on an existing window
EventDetails(wm, win, event, t)
EventDetails(wm, win, event, time())
else
nothing
end
else
nothing
end
end

function listen_for_events(wm::XWindowManager, t0, next_event::Function, execute_callback; on_iter_first=() -> nothing, on_iter_last=() -> nothing, warn_unknown=false)
while !isempty(wm.windows)
try
on_iter_first()
xge = next_event(wm)
event = unsafe_load_event(xge; warn_unknown)
t = time() - t0
ed = process_xevent(wm, event, t)
!isnothing(ed) && execute_callback(ed)
on_iter_last()
catch e
if e isa WindowException
win_callbacks = callbacks(wm, e.win)
e isa CloseWindow && win_callbacks.on_close(wm, e)
e isa InvalidWindow && win_callbacks.on_invalid(wm, e)
else
rethrow(e)
break
end
end
end
end

"""
Run an `EventLoop` attached to a `XWindowManager` instance.
"""
function Base.run(wm::XWindowManager, ::Synchronous, execute_callback = (ed) -> execute_callback(wm, ed); poll=false, kwargs...)
t0 = time()
next_event = poll ? poll_for_event : wait_for_event
listen_for_events(wm, t0, next_event, execute_callback; kwargs...)
end

function Base.run(wm::XWindowManager, ::Asynchronous, execute_callback = (ed) -> execute_callback(wm, ed); kwargs...)
t0 = time()
@async listen_for_events(wm, t0, poll_for_event, execute_callback; kwargs...)
end
13 changes: 6 additions & 7 deletions src/window_manager.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,23 @@ function set_callbacks!(wm::XWindowManager, win::XCBWindow, callbacks::WindowCal
end

function poll_for_event(wm::XWindowManager)
while true
event = xcb_poll_for_event(wm.conn)
event C_NULL && return event
yield()
end
event = xcb_poll_for_event(wm.conn)
event == C_NULL && return nothing
unsafe_load_event(event)
end

function wait_for_event(wm::XWindowManager)
event = xcb_wait_for_event(wm.conn)
event == C_NULL ? nothing : event
event == C_NULL && return nothing
unsafe_load_event(event)
end

function terminate_window!(wm::XWindowManager, win::XCBWindow)
delete!(wm.windows, win.id)
finalize(win)
end

get_window(wm::XWindowManager, id::Integer) = wm.windows[id]
get_window(wm::XWindowManager, id::Integer) = get(wm.windows, id, nothing)
get_window(wm::XWindowManager, event::xcb_xkb_state_notify_event_t) = nothing
get_window(wm::XWindowManager, event::xcb_keymap_notify_event_t) = nothing
get_window(wm::XWindowManager, event) = get_window(wm, window_id(event))
Expand Down
16 changes: 9 additions & 7 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using XCB
using Test
using UnPack

function on_button_pressed(details::EventDetails)
x, y = details.location
Expand All @@ -12,15 +11,15 @@ function on_button_pressed(details::EventDetails)
end

function on_key_pressed(wm::XWindowManager, details::EventDetails)
@unpack win, data = details
(; win, data) = details
send = XCB.send(wm, win)
km = wm.keymap
@info keystroke_info(km, details)
@unpack key_name, key, input, modifiers = data
(; key_name, key, input, modifiers) = data
kc = KeyCombination(key, modifiers)
set_title(win, "Random title $(rand())")
if kc [key"q", key"ctrl+q", key"f4"]
throw(CloseWindow(win, "Received closing request from user input"))
CloseWindow(win, "Received closing request from user input")
elseif kc == key"s"
curr_extent = XCB.extent(win)
XCB.set_extent(win, curr_extent .+ 1)
Expand Down Expand Up @@ -66,7 +65,7 @@ function test()

if is_xvfb
@info "- Running window asynchronously"
task = run(wm, Asynchronous(); warn_unknown=true)
task = @async run(wm)
@info "- Sending fake inputs"
send(MouseEvent(ButtonLeft(), MouseState(), ButtonPressed()))
send(MouseEvent(ButtonLeft(), MouseState(), ButtonReleased()))
Expand All @@ -77,9 +76,12 @@ function test()
send(key_event_from_name(wm.keymap, :AC04, KeyModifierState(), KeyPressed()))
@info "- Waiting for window to close"
wait(task)
@test !istaskfailed(task)
else
run(wm, Synchronous(); warn_unknown=true, poll=false)
run(wm)
end
end

test()
@testset "XCB.jl" begin
@test isnothing(test())
end;

0 comments on commit 66bc85a

Please sign in to comment.