diff --git a/include/control.h b/include/control.h index cbfda1d..f10060e 100644 --- a/include/control.h +++ b/include/control.h @@ -18,8 +18,14 @@ struct server; enum control_opcode { CONTROL_CREATE_GROUP_VT = 1, CONTROL_DESTROY_GROUP_VT = 2, + CONTROL_GET_ACTIVE_VT = 3, + CONTROL_FIND_FREE_VT = 4, + CONTROL_SWITCH_VT = 5, CONTROL_GROUP_VT_CREATED = 100, CONTROL_VT_CHANGED = 101, + CONTROL_OK = 102, + CONTROL_ACTIVE_VT = 103, + CONTROL_FREE_VT = 104, CONTROL_ERROR = 255, }; @@ -39,11 +45,19 @@ struct control_destroy_group_vt_request { int32_t vt; }; +struct control_switch_vt_request { + int32_t vt; +}; + struct control_group_vt_created_event { int32_t owner_pid; int32_t vt; }; +struct control_vt_state_event { + int32_t vt; +}; + struct control_vt_change_event { int32_t old_vt; int32_t new_vt; diff --git a/include/seat.h b/include/seat.h index 92c3e88..b16e241 100644 --- a/include/seat.h +++ b/include/seat.h @@ -63,6 +63,9 @@ struct seat_device *seat_find_device(struct client *client, int device_id); int seat_set_next_session(struct client *client, int session); int seat_vt_activate(struct seat *seat); int seat_vt_release(struct seat *seat); +int seat_get_active_vt(struct seat *seat); +int seat_find_available_vt(struct seat *seat); +int seat_switch_vt(struct seat *seat, int vt); int seat_create_group_vt(struct seat *seat, struct client *owner, int requested_vt, const char *user, const char *session); int seat_destroy_group_vt(struct seat *seat, int vt); diff --git a/seatd/control.c b/seatd/control.c index 3644374..ae9ce22 100644 --- a/seatd/control.c +++ b/seatd/control.c @@ -129,6 +129,17 @@ static int control_send_error(struct control_client *client, int error_code) { return control_client_flush(client); } +static int control_send_ok(struct control_client *client) { + struct control_header header = { + .opcode = CONTROL_OK, + .size = 0, + }; + if (connection_put(&client->connection, &header, sizeof(header)) == -1) { + return -1; + } + return control_client_flush(client); +} + static int control_send_message(struct control_client *client, uint16_t opcode, const void *payload, uint16_t payload_size) { struct control_header header = { @@ -150,6 +161,13 @@ static int control_send_group_vt_created(struct control_client *client, pid_t ow return control_send_message(client, CONTROL_GROUP_VT_CREATED, &event, sizeof(event)); } +static int control_send_vt_state(struct control_client *client, uint16_t opcode, int vt) { + struct control_vt_state_event event = { + .vt = vt, + }; + return control_send_message(client, opcode, &event, sizeof(event)); +} + static int control_send_vt_change(struct control_client *client, int old_vt, int new_vt) { struct control_vt_change_event event = { .old_vt = old_vt, @@ -221,6 +239,49 @@ static int handle_destroy_group_vt(struct control_client *client) { return 0; } +static int handle_get_active_vt(struct control_client *client) { + struct seat *seat = server_get_seat(client->server, "seat0"); + if (seat == NULL) { + return control_send_error(client, ENOENT); + } + + int vt = seat_get_active_vt(seat); + if (vt == -1) { + return control_send_error(client, errno); + } + return control_send_vt_state(client, CONTROL_ACTIVE_VT, vt); +} + +static int handle_find_free_vt(struct control_client *client) { + struct seat *seat = server_get_seat(client->server, "seat0"); + if (seat == NULL) { + return control_send_error(client, ENOENT); + } + + int vt = seat_find_available_vt(seat); + if (vt == -1) { + return control_send_error(client, errno); + } + return control_send_vt_state(client, CONTROL_FREE_VT, vt); +} + +static int handle_switch_vt(struct control_client *client) { + struct control_switch_vt_request request; + if (connection_get(&client->connection, &request, sizeof(request)) == -1) { + return 0; + } + + struct seat *seat = server_get_seat(client->server, "seat0"); + if (seat == NULL) { + return control_send_error(client, ENOENT); + } + + if (seat_switch_vt(seat, request.vt) == -1) { + return control_send_error(client, errno); + } + return control_send_ok(client); +} + static int control_client_handle_opcode(struct control_client *client, uint16_t opcode, uint16_t size) { switch (opcode) { @@ -234,6 +295,21 @@ static int control_client_handle_opcode(struct control_client *client, uint16_t return control_send_error(client, EPROTO); } return handle_destroy_group_vt(client); + case CONTROL_GET_ACTIVE_VT: + if (size != 0) { + return control_send_error(client, EPROTO); + } + return handle_get_active_vt(client); + case CONTROL_FIND_FREE_VT: + if (size != 0) { + return control_send_error(client, EPROTO); + } + return handle_find_free_vt(client); + case CONTROL_SWITCH_VT: + if (size != sizeof(struct control_switch_vt_request)) { + return control_send_error(client, EPROTO); + } + return handle_switch_vt(client); default: return control_send_error(client, EPROTO); } diff --git a/seatd/seat.c b/seatd/seat.c index b29aed0..96ab089 100644 --- a/seatd/seat.c +++ b/seatd/seat.c @@ -42,7 +42,7 @@ struct seat *seat_create(const char *seat_name, bool vt_bound) { linked_list_init(&seat->group_vts); seat->vt_bound = vt_bound; seat->seat_name = strdup(seat_name); - seat->cur_vt = 0; + seat->cur_vt = -1; if (seat->seat_name == NULL) { free(seat); return NULL; @@ -74,14 +74,23 @@ void seat_destroy(struct seat *seat) { free(seat); } -static void seat_update_vt(struct seat *seat) { +static int seat_update_vt(struct seat *seat) { int tty0fd = terminal_open(0); if (tty0fd == -1) { log_errorf("Could not open tty0 to update VT: %s", strerror(errno)); - return; + return -1; } - seat->cur_vt = terminal_current_vt(tty0fd); + + int vt = terminal_current_vt(tty0fd); + int saved_errno = errno; close(tty0fd); + if (vt <= 0) { + errno = saved_errno != 0 ? saved_errno : ENOENT; + return -1; + } + + seat->cur_vt = vt; + return 0; } static int vt_open(int vt) { @@ -117,8 +126,20 @@ static int vt_switch(struct seat *seat, int vt) { log_errorf("Could not open terminal to switch to VT %d: %s", vt, strerror(errno)); return -1; } - terminal_set_process_switching(ttyfd, true); - terminal_switch_vt(ttyfd, vt); + + if (terminal_set_process_switching(ttyfd, true) == -1) { + int saved_errno = errno; + close(ttyfd); + errno = saved_errno; + return -1; + } + if (terminal_switch_vt(ttyfd, vt) == -1) { + int saved_errno = errno; + close(ttyfd); + errno = saved_errno; + return -1; + } + close(ttyfd); return 0; } @@ -939,6 +960,68 @@ int seat_vt_release(struct seat *seat) { return 0; } +int seat_get_active_vt(struct seat *seat) { + if (!seat->vt_bound) { + errno = EINVAL; + return -1; + } + + if (seat_update_vt(seat) == -1) { + return -1; + } + return seat->cur_vt; +} + +int seat_find_available_vt(struct seat *seat) { + if (!seat->vt_bound) { + errno = EINVAL; + return -1; + } + + int tty0fd = terminal_open(0); + if (tty0fd == -1) { + return -1; + } + + int vt = terminal_find_available(tty0fd); + int saved_errno = errno; + close(tty0fd); + if (vt == -1) { + errno = saved_errno; + } + return vt; +} + +int seat_switch_vt(struct seat *seat, int vt) { + if (!seat->vt_bound) { + errno = EINVAL; + return -1; + } + if (vt <= 0) { + errno = EINVAL; + return -1; + } + + if (seat_update_vt(seat) == -1) { + return -1; + } + if (seat->cur_vt == vt) { + return 0; + } + + if (seat->pending_vt_switch > 0) { + errno = EBUSY; + return -1; + } + + seat->pending_vt_switch = vt; + if (vt_switch(seat, vt) == -1) { + seat->pending_vt_switch = 0; + return -1; + } + return 0; +} + int seat_create_group_vt(struct seat *seat, struct client *owner, int requested_vt, const char *user, const char *session) { if (!seat->vt_bound) {