1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 22:57:44 +00:00

Services: Renamed from Servers

It didn't feel right to have a "DHCPClient" in a "Servers" directory.
Rename this to Services to better reflect the type of programs we'll
be putting in there.
This commit is contained in:
Andreas Kling 2020-05-08 21:57:44 +02:00
parent 042b1f6814
commit cf3b58fbe8
119 changed files with 31 additions and 31 deletions

View file

@ -0,0 +1,161 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "ASClientConnection.h"
#include "ASMixer.h"
#include "AudioClientEndpoint.h"
#include <AK/SharedBuffer.h>
#include <LibAudio/Buffer.h>
#include <LibCore/EventLoop.h>
#include <errno.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
static HashMap<int, RefPtr<ASClientConnection>> s_connections;
void ASClientConnection::for_each(Function<void(ASClientConnection&)> callback)
{
NonnullRefPtrVector<ASClientConnection> connections;
for (auto& it : s_connections)
connections.append(*it.value);
for (auto& connection : connections)
callback(connection);
}
ASClientConnection::ASClientConnection(Core::LocalSocket& client_socket, int client_id, ASMixer& mixer)
: IPC::ClientConnection<AudioServerEndpoint>(*this, client_socket, client_id)
, m_mixer(mixer)
{
s_connections.set(client_id, *this);
}
ASClientConnection::~ASClientConnection()
{
}
void ASClientConnection::die()
{
s_connections.remove(client_id());
}
void ASClientConnection::did_finish_playing_buffer(Badge<ASBufferQueue>, int buffer_id)
{
post_message(Messages::AudioClient::FinishedPlayingBuffer(buffer_id));
}
void ASClientConnection::did_change_muted_state(Badge<ASMixer>, bool muted)
{
post_message(Messages::AudioClient::MutedStateChanged(muted));
}
OwnPtr<Messages::AudioServer::GreetResponse> ASClientConnection::handle(const Messages::AudioServer::Greet&)
{
return make<Messages::AudioServer::GreetResponse>(client_id());
}
OwnPtr<Messages::AudioServer::GetMainMixVolumeResponse> ASClientConnection::handle(const Messages::AudioServer::GetMainMixVolume&)
{
return make<Messages::AudioServer::GetMainMixVolumeResponse>(m_mixer.main_volume());
}
OwnPtr<Messages::AudioServer::SetMainMixVolumeResponse> ASClientConnection::handle(const Messages::AudioServer::SetMainMixVolume& message)
{
m_mixer.set_main_volume(message.volume());
return make<Messages::AudioServer::SetMainMixVolumeResponse>();
}
OwnPtr<Messages::AudioServer::EnqueueBufferResponse> ASClientConnection::handle(const Messages::AudioServer::EnqueueBuffer& message)
{
auto shared_buffer = SharedBuffer::create_from_shbuf_id(message.buffer_id());
if (!shared_buffer) {
// FIXME: The shared buffer should have been retrieved for us already.
// We don't want to do IPC error checking at this layer.
ASSERT_NOT_REACHED();
}
if (!m_queue)
m_queue = m_mixer.create_queue(*this);
if (m_queue->is_full())
return make<Messages::AudioServer::EnqueueBufferResponse>(false);
m_queue->enqueue(Audio::Buffer::create_with_shared_buffer(*shared_buffer, message.sample_count()));
return make<Messages::AudioServer::EnqueueBufferResponse>(true);
}
OwnPtr<Messages::AudioServer::GetRemainingSamplesResponse> ASClientConnection::handle(const Messages::AudioServer::GetRemainingSamples&)
{
int remaining = 0;
if (m_queue)
remaining = m_queue->get_remaining_samples();
return make<Messages::AudioServer::GetRemainingSamplesResponse>(remaining);
}
OwnPtr<Messages::AudioServer::GetPlayedSamplesResponse> ASClientConnection::handle(const Messages::AudioServer::GetPlayedSamples&)
{
int played = 0;
if (m_queue)
played = m_queue->get_played_samples();
return make<Messages::AudioServer::GetPlayedSamplesResponse>(played);
}
OwnPtr<Messages::AudioServer::SetPausedResponse> ASClientConnection::handle(const Messages::AudioServer::SetPaused& message)
{
if (m_queue)
m_queue->set_paused(message.paused());
return make<Messages::AudioServer::SetPausedResponse>();
}
OwnPtr<Messages::AudioServer::ClearBufferResponse> ASClientConnection::handle(const Messages::AudioServer::ClearBuffer& message)
{
if (m_queue)
m_queue->clear(message.paused());
return make<Messages::AudioServer::ClearBufferResponse>();
}
OwnPtr<Messages::AudioServer::GetPlayingBufferResponse> ASClientConnection::handle(const Messages::AudioServer::GetPlayingBuffer&)
{
int id = -1;
if (m_queue)
id = m_queue->get_playing_buffer();
return make<Messages::AudioServer::GetPlayingBufferResponse>(id);
}
OwnPtr<Messages::AudioServer::GetMutedResponse> ASClientConnection::handle(const Messages::AudioServer::GetMuted&)
{
return make<Messages::AudioServer::GetMutedResponse>(m_mixer.is_muted());
}
OwnPtr<Messages::AudioServer::SetMutedResponse> ASClientConnection::handle(const Messages::AudioServer::SetMuted& message)
{
m_mixer.set_muted(message.muted());
return make<Messages::AudioServer::SetMutedResponse>();
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/HashMap.h>
#include <AudioServer/AudioServerEndpoint.h>
#include <LibIPC/ClientConnection.h>
namespace Audio {
class Buffer;
}
class ASBufferQueue;
class ASMixer;
class ASClientConnection final : public IPC::ClientConnection<AudioServerEndpoint>
, public AudioServerEndpoint {
C_OBJECT(ASClientConnection)
public:
explicit ASClientConnection(Core::LocalSocket&, int client_id, ASMixer& mixer);
~ASClientConnection() override;
void did_finish_playing_buffer(Badge<ASBufferQueue>, int buffer_id);
void did_change_muted_state(Badge<ASMixer>, bool muted);
virtual void die() override;
static void for_each(Function<void(ASClientConnection&)>);
private:
virtual OwnPtr<Messages::AudioServer::GreetResponse> handle(const Messages::AudioServer::Greet&) override;
virtual OwnPtr<Messages::AudioServer::GetMainMixVolumeResponse> handle(const Messages::AudioServer::GetMainMixVolume&) override;
virtual OwnPtr<Messages::AudioServer::SetMainMixVolumeResponse> handle(const Messages::AudioServer::SetMainMixVolume&) override;
virtual OwnPtr<Messages::AudioServer::EnqueueBufferResponse> handle(const Messages::AudioServer::EnqueueBuffer&) override;
virtual OwnPtr<Messages::AudioServer::GetRemainingSamplesResponse> handle(const Messages::AudioServer::GetRemainingSamples&) override;
virtual OwnPtr<Messages::AudioServer::GetPlayedSamplesResponse> handle(const Messages::AudioServer::GetPlayedSamples&) override;
virtual OwnPtr<Messages::AudioServer::SetPausedResponse> handle(const Messages::AudioServer::SetPaused&) override;
virtual OwnPtr<Messages::AudioServer::ClearBufferResponse> handle(const Messages::AudioServer::ClearBuffer&) override;
virtual OwnPtr<Messages::AudioServer::GetPlayingBufferResponse> handle(const Messages::AudioServer::GetPlayingBuffer&) override;
virtual OwnPtr<Messages::AudioServer::GetMutedResponse> handle(const Messages::AudioServer::GetMuted&) override;
virtual OwnPtr<Messages::AudioServer::SetMutedResponse> handle(const Messages::AudioServer::SetMuted&) override;
ASMixer& m_mixer;
RefPtr<ASBufferQueue> m_queue;
};

View file

@ -0,0 +1,152 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <AK/BufferStream.h>
#include <AK/NumericLimits.h>
#include <AudioServer/ASClientConnection.h>
#include <AudioServer/ASMixer.h>
#include <pthread.h>
ASMixer::ASMixer()
: m_device(Core::File::construct("/dev/audio", this))
, m_sound_thread(
[this] {
mix();
return 0;
},
"AudioServer[mixer]")
{
if (!m_device->open(Core::IODevice::WriteOnly)) {
dbgprintf("Can't open audio device: %s\n", m_device->error_string());
return;
}
pthread_mutex_init(&m_pending_mutex, nullptr);
pthread_cond_init(&m_pending_cond, nullptr);
m_zero_filled_buffer = (u8*)malloc(4096);
bzero(m_zero_filled_buffer, 4096);
m_sound_thread.start();
}
ASMixer::~ASMixer()
{
}
NonnullRefPtr<ASBufferQueue> ASMixer::create_queue(ASClientConnection& client)
{
auto queue = adopt(*new ASBufferQueue(client));
pthread_mutex_lock(&m_pending_mutex);
m_pending_mixing.append(*queue);
pthread_cond_signal(&m_pending_cond);
pthread_mutex_unlock(&m_pending_mutex);
return queue;
}
void ASMixer::mix()
{
decltype(m_pending_mixing) active_mix_queues;
for (;;) {
if (active_mix_queues.is_empty()) {
pthread_mutex_lock(&m_pending_mutex);
pthread_cond_wait(&m_pending_cond, &m_pending_mutex);
active_mix_queues.append(move(m_pending_mixing));
pthread_mutex_unlock(&m_pending_mutex);
}
active_mix_queues.remove_all_matching([&](auto& entry) { return !entry->client(); });
Audio::Sample mixed_buffer[1024];
auto mixed_buffer_length = (int)(sizeof(mixed_buffer) / sizeof(Audio::Sample));
// Mix the buffers together into the output
for (auto& queue : active_mix_queues) {
if (!queue->client()) {
queue->clear();
continue;
}
for (int i = 0; i < mixed_buffer_length; ++i) {
auto& mixed_sample = mixed_buffer[i];
Audio::Sample sample;
if (!queue->get_next_sample(sample))
break;
mixed_sample += sample;
}
}
bool muted = m_muted;
// output the mixed stuff to the device
u8 raw_buffer[4096];
auto buffer = ByteBuffer::wrap(muted ? m_zero_filled_buffer : raw_buffer, sizeof(raw_buffer));
BufferStream stream(buffer);
if (!muted) {
for (int i = 0; i < mixed_buffer_length; ++i) {
auto& mixed_sample = mixed_buffer[i];
mixed_sample.scale(m_main_volume);
mixed_sample.clip();
i16 out_sample;
out_sample = mixed_sample.left * NumericLimits<i16>::max();
stream << out_sample;
ASSERT(!stream.at_end()); // we should have enough space for both channels in one buffer!
out_sample = mixed_sample.right * NumericLimits<i16>::max();
stream << out_sample;
}
}
if (stream.offset() != 0) {
buffer.trim(stream.offset());
}
m_device->write(buffer);
}
}
void ASMixer::set_muted(bool muted)
{
if (m_muted == muted)
return;
m_muted = muted;
ASClientConnection::for_each([muted](ASClientConnection& client) {
client.did_change_muted_state({}, muted);
});
}
ASBufferQueue::ASBufferQueue(ASClientConnection& client)
: m_client(client.make_weak_ptr())
{
}
void ASBufferQueue::enqueue(NonnullRefPtr<Audio::Buffer>&& buffer)
{
m_remaining_samples += buffer->sample_count();
m_queue.enqueue(move(buffer));
}

View file

@ -0,0 +1,139 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include "ASClientConnection.h"
#include <AK/Badge.h>
#include <AK/ByteBuffer.h>
#include <AK/NonnullRefPtrVector.h>
#include <AK/Queue.h>
#include <AK/RefCounted.h>
#include <AK/WeakPtr.h>
#include <LibAudio/Buffer.h>
#include <LibCore/File.h>
#include <LibThread/Lock.h>
#include <LibThread/Thread.h>
class ASClientConnection;
class ASBufferQueue : public RefCounted<ASBufferQueue> {
public:
explicit ASBufferQueue(ASClientConnection&);
~ASBufferQueue() {}
bool is_full() const { return m_queue.size() >= 3; }
void enqueue(NonnullRefPtr<Audio::Buffer>&&);
bool get_next_sample(Audio::Sample& sample)
{
if (m_paused)
return false;
while (!m_current && !m_queue.is_empty())
m_current = m_queue.dequeue();
if (!m_current)
return false;
sample = m_current->samples()[m_position++];
--m_remaining_samples;
++m_played_samples;
if (m_position >= m_current->sample_count()) {
m_client->did_finish_playing_buffer({}, m_current->shbuf_id());
m_current = nullptr;
m_position = 0;
}
return true;
}
ASClientConnection* client() { return m_client.ptr(); }
void clear(bool paused = false)
{
m_queue.clear();
m_position = 0;
m_remaining_samples = 0;
m_played_samples = 0;
m_current = nullptr;
m_paused = paused;
}
void set_paused(bool paused)
{
m_paused = paused;
}
int get_remaining_samples() const { return m_remaining_samples; }
int get_played_samples() const { return m_played_samples; }
int get_playing_buffer() const
{
if (m_current)
return m_current->shbuf_id();
return -1;
}
private:
RefPtr<Audio::Buffer> m_current;
Queue<NonnullRefPtr<Audio::Buffer>> m_queue;
int m_position { 0 };
int m_remaining_samples { 0 };
int m_played_samples { 0 };
bool m_paused { false };
WeakPtr<ASClientConnection> m_client;
};
class ASMixer : public Core::Object {
C_OBJECT(ASMixer)
public:
ASMixer();
virtual ~ASMixer() override;
NonnullRefPtr<ASBufferQueue> create_queue(ASClientConnection&);
int main_volume() const { return m_main_volume; }
void set_main_volume(int volume) { m_main_volume = volume; }
bool is_muted() const { return m_muted; }
void set_muted(bool);
private:
Vector<NonnullRefPtr<ASBufferQueue>> m_pending_mixing;
pthread_mutex_t m_pending_mutex;
pthread_cond_t m_pending_cond;
RefPtr<Core::File> m_device;
LibThread::Thread m_sound_thread;
bool m_muted { false };
int m_main_volume { 100 };
u8* m_zero_filled_buffer { nullptr };
void mix();
};

View file

@ -0,0 +1,5 @@
endpoint AudioClient = 82
{
FinishedPlayingBuffer(i32 buffer_id) =|
MutedStateChanged(bool muted) =|
}

View file

@ -0,0 +1,21 @@
endpoint AudioServer = 85
{
// Basic protocol
Greet() => (i32 client_id)
// Mixer functions
SetMuted(bool muted) => ()
GetMuted() => (bool muted)
GetMainMixVolume() => (i32 volume)
SetMainMixVolume(i32 volume) => ()
// Buffer playback
EnqueueBuffer(i32 buffer_id, int sample_count) => (bool success)
SetPaused(bool paused) => ()
ClearBuffer(bool paused) => ()
//Buffer information
GetRemainingSamples() => (int remaining_samples)
GetPlayedSamples() => (int played_samples)
GetPlayingBuffer() => (i32 buffer_id)
}

View file

@ -0,0 +1,24 @@
OBJS = \
main.o \
ASMixer.o \
ASClientConnection.o
PROGRAM = AudioServer
LIB_DEPS = Core IPC Thread Pthread
EXTRA_CLEAN = AudioServerEndpoint.h AudioClientEndpoint.h
*.cpp: AudioServerEndpoint.h AudioClientEndpoint.h
AudioServerEndpoint.h: AudioServer.ipc | IPCCOMPILER
@echo "IPC $<"; $(IPCCOMPILER) $< > $@
AudioClientEndpoint.h: AudioClient.ipc | IPCCOMPILER
@echo "IPC $<"; $(IPCCOMPILER) $< > $@
install:
mkdir -p $(SERENITY_BASE_DIR)/Root/usr/include/AudioServer/
cp *.h $(SERENITY_BASE_DIR)/Root/usr/include/AudioServer/
include ../../Makefile.common

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "ASMixer.h"
#include <LibCore/File.h>
#include <LibCore/LocalServer.h>
int main(int, char**)
{
if (pledge("stdio thread shared_buffer accept rpath wpath cpath unix fattr", nullptr) < 0) {
perror("pledge");
return 1;
}
Core::EventLoop event_loop;
ASMixer mixer;
auto server = Core::LocalServer::construct();
bool ok = server->take_over_from_system_server();
ASSERT(ok);
server->on_ready_to_accept = [&] {
auto client_socket = server->accept();
if (!client_socket) {
dbg() << "AudioServer: accept failed.";
return;
}
static int s_next_client_id = 0;
int client_id = ++s_next_client_id;
IPC::new_client_connection<ASClientConnection>(*client_socket, client_id, mixer);
};
if (pledge("stdio thread shared_buffer accept", nullptr) < 0) {
perror("pledge");
return 1;
}
return event_loop.exec();
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "DHCPv4.h"
//#define DHCPV4_DEBUG
ParsedDHCPv4Options DHCPv4Packet::parse_options() const
{
ParsedDHCPv4Options options;
for (size_t index = 4; index < DHCPV4_OPTION_FIELD_MAX_LENGTH; ++index) {
auto opt_name = *(const DHCPOption*)&m_options[index];
switch (opt_name) {
case DHCPOption::Pad:
continue;
case DHCPOption::End:
goto escape;
default:
++index;
auto length = m_options[index];
if ((size_t)length > DHCPV4_OPTION_FIELD_MAX_LENGTH - index) {
dbg() << "Bogus option length " << length << " assuming forgotten END";
break;
}
#ifdef DHCPV4_DEBUG
dbg() << "DHCP Option " << (u8)opt_name << " with length " << length;
#endif
++index;
options.options.set(opt_name, { length, &m_options[index] });
index += length - 1;
break;
}
}
escape:;
return options;
}

View file

@ -0,0 +1,300 @@
/*
* Copyright (c) 2020, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/Assertions.h>
#include <AK/ByteBuffer.h>
#include <AK/HashMap.h>
#include <AK/IPv4Address.h>
#include <AK/MACAddress.h>
#include <AK/NetworkOrdered.h>
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#include <AK/Traits.h>
#include <AK/Types.h>
#include <string.h>
enum class DHCPv4Flags : u16 {
Broadcast = 1,
/* everything else is reserved and must be zero */
};
enum class DHCPv4Op : u8 {
BootRequest = 1,
BootReply = 2
};
enum class DHCPOption : u8 {
// BOOTP
Pad = 0,
SubnetMask,
TimeOffset,
Router,
TimeServer,
NameServer,
DomainNameServer,
LogServer,
CookieServer,
LPRServer,
ImpressServer,
ResourceLocationServer,
HostName,
BootFileSize,
MeritDumpFile,
DomainName,
SwapServer,
RootPath,
ExtensionsPath,
IPForwardingEnableDisable,
NonLocalSourceRoutingEnableDisable,
PolicyFilter,
MaximumDatagramReassemblySize,
DefaultIPTTL,
PathMTUAgingTimeout,
PathMTUPlateauTable,
InterfaceMTU,
AllSubnetsAreLocal,
BroadcastAddress,
PerformMaskDiscovery,
MaskSupplier,
PerformRouterDiscovery,
RouterSolicitationAddress,
StaticRoute,
TrailerEncapsulation,
ARPCacheTimeout,
EthernetEncapsulation,
TCPDefaultTTL,
TCPKeepaliveInterval,
TCPKeepaliveGarbage,
NetworkInformationServiceDomain,
NetworkInformationServers,
NetworkTimeProtocolServers,
VendorSpecificInformation,
NetBIOSOverTCPIPNameServer,
NetBIOSOverTCPIPDatagramDistributionServer,
NetBIOSOverTCPIPNodeType,
NetBIOSOverTCPIPScope,
XWindowSystemFontServer, // wow
XWindowSystemDisplayManager,
// DHCP
RequestedIPAddress = 50,
IPAddressLeaseTime,
OptionOverload,
DHCPMessageType,
ServerIdentifier,
ParameterRequestList,
Message,
MaximumDHCPMessageSize,
RenewalT1Time,
RenewalT2Time,
ClassIdentifier,
ClientIdentifier,
End = 255
};
enum class DHCPMessageType : u8 {
DHCPDiscover = 1,
DHCPOffer,
DHCPRequest,
DHCPDecline,
DHCPAck,
DHCPNak,
DHCPRelease,
};
template <>
struct AK::Traits<DHCPOption> : public AK::GenericTraits<DHCPOption> {
static constexpr bool is_trivial() { return true; }
static unsigned hash(DHCPOption u) { return int_hash((u8)u); }
};
struct ParsedDHCPv4Options {
template <typename T>
Optional<const T> get(DHCPOption option_name) const
{
auto option = options.get(option_name);
if (!option.has_value()) {
return {};
}
auto& value = option.value();
if (value.length != sizeof(T))
return {};
return *(const T*)value.value;
}
template <typename T>
Vector<T> get_many(DHCPOption option_name, size_t max_number) const
{
Vector<T> values;
auto option = options.get(option_name);
if (!option.has_value()) {
return {};
}
auto& value = option.value();
if (value.length < sizeof(T))
return {};
for (size_t i = 0; i < max_number; ++i) {
auto offset = i * sizeof(T);
if (offset >= value.length)
break;
values.append(*(T*)((u8*)const_cast<void*>(value.value) + offset));
}
return values;
}
String to_string() const
{
StringBuilder builder;
builder.append("DHCP Options (");
builder.appendf("%d", options.size());
builder.append(" entries)\n");
for (auto& opt : options) {
builder.appendf("\toption %d (%d bytes):", (u8)opt.key, (u8)opt.value.length);
for (auto i = 0; i < opt.value.length; ++i)
builder.appendf(" %u ", ((const u8*)opt.value.value)[i]);
builder.append('\n');
}
return builder.build();
}
struct DHCPOptionValue {
u8 length;
const void* value;
};
HashMap<DHCPOption, DHCPOptionValue> options;
};
constexpr auto DHCPV4_OPTION_FIELD_MAX_LENGTH = 312;
class [[gnu::packed]] DHCPv4Packet
{
public:
u8 op() const { return m_op; }
void set_op(DHCPv4Op op) { m_op = (u8)op; }
u8 htype() const { return m_htype; }
void set_htype(u8 htype) { m_htype = htype; }
u8 hlen() const { return m_hlen; }
void set_hlen(u8 hlen) { m_hlen = hlen; }
u8 hops() const { return m_hops; }
void set_hops(u8 hops) { m_hops = hops; }
u32 xid() const { return m_xid; }
void set_xid(u32 xid) { m_xid = xid; }
u16 secs() const { return m_secs; }
void set_secs(u16 secs) { m_secs = secs; }
u16 flags() const { return m_flags; }
void set_flags(DHCPv4Flags flags) { m_flags = (u16)flags; }
const IPv4Address& ciaddr() const { return m_ciaddr; }
const IPv4Address& yiaddr() const { return m_yiaddr; }
const IPv4Address& siaddr() const { return m_siaddr; }
const IPv4Address& giaddr() const { return m_giaddr; }
IPv4Address& ciaddr() { return m_ciaddr; }
IPv4Address& yiaddr() { return m_yiaddr; }
IPv4Address& siaddr() { return m_siaddr; }
IPv4Address& giaddr() { return m_giaddr; }
u8* options() { return m_options; }
ParsedDHCPv4Options parse_options() const;
const MACAddress& chaddr() const { return *(const MACAddress*)&m_chaddr[0]; }
void set_chaddr(const MACAddress& mac) { *(MACAddress*)&m_chaddr[0] = mac; }
StringView sname() const { return { (const char*)&m_sname[0] }; }
StringView file() const { return { (const char*)&m_file[0] }; }
private:
NetworkOrdered<u8> m_op;
NetworkOrdered<u8> m_htype;
NetworkOrdered<u8> m_hlen;
NetworkOrdered<u8> m_hops;
NetworkOrdered<u32> m_xid;
NetworkOrdered<u16> m_secs;
NetworkOrdered<u16> m_flags;
IPv4Address m_ciaddr;
IPv4Address m_yiaddr;
IPv4Address m_siaddr;
IPv4Address m_giaddr;
u8 m_chaddr[16]; // 10 bytes of padding at the end
u8 m_sname[64] { 0 };
u8 m_file[128] { 0 };
u8 m_options[DHCPV4_OPTION_FIELD_MAX_LENGTH] { 0 }; // variable, less than 312 bytes
};
class DHCPv4PacketBuilder {
public:
DHCPv4PacketBuilder()
: m_buffer(ByteBuffer::create_zeroed(sizeof(DHCPv4Packet)))
{
auto* options = peek().options();
// set the magic DHCP cookie value
options[0] = 99;
options[1] = 130;
options[2] = 83;
options[3] = 99;
}
void add_option(DHCPOption option, u8 length, const void* data)
{
ASSERT(m_can_add);
// we need enough space to fit the option value, its length, and its data
ASSERT(next_option_offset + length + 2 < DHCPV4_OPTION_FIELD_MAX_LENGTH);
auto* options = peek().options();
options[next_option_offset++] = (u8)option;
memcpy(options + next_option_offset, &length, 1);
next_option_offset++;
memcpy(options + next_option_offset, data, length);
next_option_offset += length;
}
void set_message_type(DHCPMessageType type) { add_option(DHCPOption::DHCPMessageType, 1, &type); }
DHCPv4Packet& peek() { return *(DHCPv4Packet*)m_buffer.data(); }
DHCPv4Packet& build()
{
add_option(DHCPOption::End, 0, nullptr);
m_can_add = false;
return *(DHCPv4Packet*)m_buffer.data();
}
size_t size() const { return m_buffer.size(); }
private:
ByteBuffer m_buffer;
size_t next_option_offset { 4 };
bool m_can_add { true };
};

View file

@ -0,0 +1,283 @@
/*
* Copyright (c) 2020, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "DHCPv4Client.h"
#include <AK/ByteBuffer.h>
#include <AK/Function.h>
#include <LibCore/SocketAddress.h>
#include <LibCore/Timer.h>
#include <stdio.h>
//#define DHCPV4CLIENT_DEBUG
static void send(const InterfaceDescriptor& iface, const DHCPv4Packet& packet, Core::Object*)
{
int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (fd < 0) {
dbg() << "ERROR: socket :: " << strerror(errno);
return;
}
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, iface.m_ifname.characters(), IFNAMSIZ) < 0) {
dbg() << "ERROR: setsockopt(SO_BINDTODEVICE) :: " << strerror(errno);
return;
}
sockaddr_in dst;
memset(&dst, 0, sizeof(dst));
dst.sin_family = AF_INET;
dst.sin_port = htons(67);
dst.sin_addr.s_addr = IPv4Address { 255, 255, 255, 255 }.to_u32();
memset(&dst.sin_zero, 0, sizeof(dst.sin_zero));
auto rc = sendto(fd, &packet, sizeof(packet), 0, (sockaddr*)&dst, sizeof(dst));
if (rc < 0) {
dbg() << "sendto failed with " << strerror(errno);
// FIXME: what do we do here?
}
}
static void set_params(const InterfaceDescriptor& iface, const IPv4Address& ipv4_addr, const IPv4Address& netmask, const IPv4Address& gateway)
{
int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (fd < 0) {
dbg() << "ERROR: socket :: " << strerror(errno);
return;
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, iface.m_ifname.characters(), IFNAMSIZ);
// set the IP address
ifr.ifr_addr.sa_family = AF_INET;
((sockaddr_in&)ifr.ifr_addr).sin_addr.s_addr = ipv4_addr.to_in_addr_t();
if (ioctl(fd, SIOCSIFADDR, &ifr) < 0) {
dbg() << "ERROR: ioctl(SIOCSIFADDR) :: " << strerror(errno);
}
// set the network mask
((sockaddr_in&)ifr.ifr_netmask).sin_addr.s_addr = netmask.to_in_addr_t();
if (ioctl(fd, SIOCSIFNETMASK, &ifr) < 0) {
dbg() << "ERROR: ioctl(SIOCSIFNETMASK) :: " << strerror(errno);
}
// set the default gateway
struct rtentry rt;
memset(&rt, 0, sizeof(rt));
rt.rt_dev = const_cast<char*>(iface.m_ifname.characters());
rt.rt_gateway.sa_family = AF_INET;
((sockaddr_in&)rt.rt_gateway).sin_addr.s_addr = gateway.to_in_addr_t();
rt.rt_flags = RTF_UP | RTF_GATEWAY;
if (ioctl(fd, SIOCADDRT, &rt) < 0) {
dbg() << "Error: ioctl(SIOCADDRT) :: " << strerror(errno);
}
}
DHCPv4Client::DHCPv4Client(Vector<InterfaceDescriptor> ifnames)
: m_ifnames(ifnames)
{
m_server = Core::UDPServer::construct(this);
m_server->on_ready_to_receive = [this] {
auto buffer = m_server->receive(sizeof(DHCPv4Packet));
dbg() << "Received " << buffer.size() << " bytes";
if (buffer.size() != sizeof(DHCPv4Packet)) {
dbg() << "we expected " << sizeof(DHCPv4Packet) << " bytes, this is a bad packet";
return;
}
auto& packet = *(DHCPv4Packet*)buffer.data();
process_incoming(packet);
};
if (!m_server->bind({}, 68)) {
dbg() << "The server we just created somehow came already bound, refusing to continue";
ASSERT_NOT_REACHED();
}
for (auto& iface : m_ifnames)
dhcp_discover(iface);
}
DHCPv4Client::~DHCPv4Client()
{
}
void DHCPv4Client::handle_offer(const DHCPv4Packet& packet, const ParsedDHCPv4Options& options)
{
dbg() << "We were offered " << packet.yiaddr().to_string() << " for " << options.get<u32>(DHCPOption::IPAddressLeaseTime).value_or(0);
auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
if (!transaction) {
dbg() << "we're not looking for " << packet.xid();
return;
}
if (transaction->has_ip)
return;
if (transaction->accepted_offer) {
// we've accepted someone's offer, but they haven't given us an ack
// TODO: maybe record this offer?
return;
}
// TAKE IT...
transaction->offered_lease_time = options.get<u32>(DHCPOption::IPAddressLeaseTime).value();
dhcp_request(*transaction, packet);
}
void DHCPv4Client::handle_ack(const DHCPv4Packet& packet, const ParsedDHCPv4Options& options)
{
#ifdef DHCPV4CLIENT_DEBUG
dbg() << "The DHCP server handed us " << packet.yiaddr().to_string();
dbg() << "Here are the options: " << options.to_string();
#endif
auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
if (!transaction) {
dbg() << "we're not looking for " << packet.xid();
return;
}
transaction->has_ip = true;
auto& interface = transaction->interface;
auto new_ip = packet.yiaddr();
auto lease_time = convert_between_host_and_network(options.get<u32>(DHCPOption::IPAddressLeaseTime).value_or(transaction->offered_lease_time));
// set a timer for the duration of the lease, we shall renew if needed
Core::Timer::create_single_shot(
lease_time * 1000,
[this, transaction, interface = InterfaceDescriptor { interface }, new_ip] {
transaction->accepted_offer = false;
transaction->has_ip = false;
dhcp_discover(interface, new_ip);
},
this);
set_params(transaction->interface, new_ip, options.get<IPv4Address>(DHCPOption::SubnetMask).value(), options.get_many<IPv4Address>(DHCPOption::Router, 1).first());
}
void DHCPv4Client::handle_nak(const DHCPv4Packet& packet, const ParsedDHCPv4Options& options)
{
dbg() << "The DHCP server told us to go chase our own tail about " << packet.yiaddr().to_string();
dbg() << "Here are the options: " << options.to_string();
// make another request a bit later :shrug:
auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
if (!transaction) {
dbg() << "we're not looking for " << packet.xid();
return;
}
transaction->accepted_offer = false;
transaction->has_ip = false;
auto& iface = transaction->interface;
Core::Timer::create_single_shot(
10000,
[this, iface = InterfaceDescriptor { iface }] {
dhcp_discover(iface);
},
this);
}
void DHCPv4Client::process_incoming(const DHCPv4Packet& packet)
{
auto options = packet.parse_options();
#ifdef DHCPV4CLIENT_DEBUG
dbg() << "Here are the options: " << options.to_string();
#endif
auto value = options.get<DHCPMessageType>(DHCPOption::DHCPMessageType).value();
switch (value) {
case DHCPMessageType::DHCPOffer:
handle_offer(packet, options);
break;
case DHCPMessageType::DHCPAck:
handle_ack(packet, options);
break;
case DHCPMessageType::DHCPNak:
handle_nak(packet, options);
break;
case DHCPMessageType::DHCPDiscover:
case DHCPMessageType::DHCPRequest:
case DHCPMessageType::DHCPRelease:
// These are not for us
// we're just getting them because there are other people on our subnet
// broadcasting stuff
break;
case DHCPMessageType::DHCPDecline:
default:
dbg() << "I dunno what to do with this " << (u8)value;
ASSERT_NOT_REACHED();
break;
}
}
void DHCPv4Client::dhcp_discover(const InterfaceDescriptor& iface, IPv4Address previous)
{
auto transaction_id = rand();
#ifdef DHCPV4CLIENT_DEBUG
dbg() << "Trying to lease an IP for " << iface.m_ifname << " with ID " << transaction_id;
if (!previous.is_zero())
dbg() << "going to request the server to hand us " << previous.to_string();
#endif
DHCPv4PacketBuilder builder;
DHCPv4Packet& packet = builder.peek();
packet.set_op(DHCPv4Op::BootRequest);
packet.set_htype(1); // 10mb ethernet
packet.set_hlen(sizeof(MACAddress));
packet.set_xid(transaction_id);
packet.set_flags(DHCPv4Flags::Broadcast);
packet.ciaddr() = previous;
packet.set_chaddr(iface.m_mac_address);
packet.set_secs(65535); // we lie
// set packet options
builder.set_message_type(DHCPMessageType::DHCPDiscover);
auto& dhcp_packet = builder.build();
// broadcast the discover request
send(iface, dhcp_packet, this);
m_ongoing_transactions.set(transaction_id, make<DHCPv4Transaction>(iface));
}
void DHCPv4Client::dhcp_request(DHCPv4Transaction& transaction, const DHCPv4Packet& offer)
{
auto& iface = transaction.interface;
dbg() << "Leasing the IP " << offer.yiaddr().to_string() << " for adapter " << iface.m_ifname;
DHCPv4PacketBuilder builder;
DHCPv4Packet& packet = builder.peek();
packet.set_op(DHCPv4Op::BootRequest);
packet.set_htype(1); // 10mb ethernet
packet.set_hlen(sizeof(MACAddress));
packet.set_xid(offer.xid());
packet.set_flags(DHCPv4Flags::Broadcast);
packet.set_chaddr(iface.m_mac_address);
packet.set_secs(65535); // we lie
// set packet options
builder.set_message_type(DHCPMessageType::DHCPRequest);
auto& dhcp_packet = builder.build();
// broadcast the "request" request
send(iface, dhcp_packet, this);
transaction.accepted_offer = true;
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2020, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include "DHCPv4.h"
#include <AK/FlyString.h>
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
#include <AK/Vector.h>
#include <LibCore/UDPServer.h>
#include <LibCore/UDPSocket.h>
#include <net/if.h>
#include <net/route.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
struct InterfaceDescriptor {
FlyString m_ifname;
MACAddress m_mac_address;
};
struct DHCPv4Transaction {
DHCPv4Transaction(InterfaceDescriptor ifname)
: interface(ifname)
{
}
InterfaceDescriptor interface;
bool accepted_offer { false };
bool has_ip { false };
u32 offered_lease_time { 0 };
};
class DHCPv4Client final : public Core::Object {
C_OBJECT(DHCPv4Client)
public:
explicit DHCPv4Client(Vector<InterfaceDescriptor> ifnames);
virtual ~DHCPv4Client() override;
void dhcp_discover(const InterfaceDescriptor& ifname, IPv4Address previous = IPv4Address { 0, 0, 0, 0 });
void dhcp_request(DHCPv4Transaction& transaction, const DHCPv4Packet& packet);
void process_incoming(const DHCPv4Packet& packet);
bool id_is_registered(u32 id) { return m_ongoing_transactions.contains(id); }
private:
HashMap<u32, OwnPtr<DHCPv4Transaction>> m_ongoing_transactions;
Vector<InterfaceDescriptor> m_ifnames;
RefPtr<Core::UDPServer> m_server;
void handle_offer(const DHCPv4Packet&, const ParsedDHCPv4Options&);
void handle_ack(const DHCPv4Packet&, const ParsedDHCPv4Options&);
void handle_nak(const DHCPv4Packet&, const ParsedDHCPv4Options&);
};

View file

@ -0,0 +1,10 @@
OBJS = \
DHCPv4.o \
DHCPv4Client.o \
main.o
PROGRAM = DHCPClient
LIB_DEPS = Core
include ../../Makefile.common

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2020, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "DHCPv4Client.h"
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/String.h>
#include <AK/Types.h>
#include <LibCore/EventLoop.h>
#include <LibCore/File.h>
#include <LibCore/LocalServer.h>
#include <stdio.h>
#include <string.h>
static u8 mac_part(const Vector<String>& parts, size_t index)
{
auto chars = parts.at(index).characters();
return (chars[0] - '0') * 16 + (chars[1] - '0');
}
static MACAddress mac_from_string(const String& str)
{
auto chunks = str.split(':');
ASSERT(chunks.size() == 6); // should we...worry about this?
return {
mac_part(chunks, 0), mac_part(chunks, 1), mac_part(chunks, 2),
mac_part(chunks, 3), mac_part(chunks, 4), mac_part(chunks, 5)
};
}
int main(int argc, char** argv)
{
(void)argc;
(void)argv;
if (pledge("stdio unix inet cpath rpath fattr", nullptr) < 0) {
perror("pledge");
return 1;
}
Core::EventLoop event_loop;
if (unveil("/proc/net/", "r") < 0) {
perror("unveil");
return 1;
}
unveil(nullptr, nullptr);
auto file = Core::File::construct("/proc/net/adapters");
if (!file->open(Core::IODevice::ReadOnly)) {
fprintf(stderr, "Error: %s\n", file->error_string());
return 1;
}
auto file_contents = file->read_all();
auto json = JsonValue::from_string(file_contents).as_array();
Vector<InterfaceDescriptor> ifnames;
json.for_each([&ifnames](auto& value) {
auto if_object = value.as_object();
if (if_object.get("class_name").to_string() == "LoopbackAdapter")
return;
auto name = if_object.get("name").to_string();
auto mac = if_object.get("mac_address").to_string();
ifnames.append({ name, mac_from_string(mac) });
});
auto client = DHCPv4Client::construct(move(ifnames));
if (pledge("stdio inet", nullptr) < 0) {
perror("pledge");
return 1;
}
return event_loop.exec();
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "DNSAnswer.h"
#include <time.h>
DNSAnswer::DNSAnswer(const String& name, u16 type, u16 class_code, u32 ttl, const String& record_data)
: m_name(name)
, m_type(type)
, m_class_code(class_code)
, m_ttl(ttl)
, m_record_data(record_data)
{
auto now = time(nullptr);
m_expiration_time = now + m_ttl;
if (m_expiration_time < now)
m_expiration_time = 0;
}
bool DNSAnswer::has_expired() const
{
return time(nullptr) >= m_expiration_time;
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/String.h>
#include <AK/Types.h>
class DNSAnswer {
public:
DNSAnswer(const String& name, u16 type, u16 class_code, u32 ttl, const String& record_data);
const String& name() const { return m_name; }
u16 type() const { return m_type; }
u16 class_code() const { return m_class_code; }
u32 ttl() const { return m_ttl; }
const String& record_data() const { return m_record_data; }
bool has_expired() const;
private:
String m_name;
u16 m_type { 0 };
u16 m_class_code { 0 };
u32 m_ttl { 0 };
time_t m_expiration_time { 0 };
String m_record_data;
};

View file

@ -0,0 +1,116 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/NetworkOrdered.h>
#include <AK/Types.h>
class [[gnu::packed]] DNSPacket
{
public:
DNSPacket()
: m_recursion_desired(false)
, m_truncated(false)
, m_authoritative_answer(false)
, m_opcode(0)
, m_query_or_response(false)
, m_response_code(0)
, m_checking_disabled(false)
, m_authenticated_data(false)
, m_zero(false)
, m_recursion_available(false)
{
}
u16 id() const { return m_id; }
void set_id(u16 w) { m_id = w; }
bool recursion_desired() const { return m_recursion_desired; }
void set_recursion_desired(bool b) { m_recursion_desired = b; }
bool is_truncated() const { return m_truncated; }
void set_truncated(bool b) { m_truncated = b; }
bool is_authoritative_answer() const { return m_authoritative_answer; }
void set_authoritative_answer(bool b) { m_authoritative_answer = b; }
u8 opcode() const { return m_opcode; }
void set_opcode(u8 b) { m_opcode = b; }
bool is_query() const { return !m_query_or_response; }
bool is_response() const { return m_query_or_response; }
void set_is_query() { m_query_or_response = false; }
void set_is_response() { m_query_or_response = true; }
u8 response_code() const { return m_response_code; }
void set_response_code(u8 b) { m_response_code = b; }
bool checking_disabled() const { return m_checking_disabled; }
void set_checking_disabled(bool b) { m_checking_disabled = b; }
bool is_authenticated_data() const { return m_authenticated_data; }
void set_authenticated_data(bool b) { m_authenticated_data = b; }
bool is_recursion_available() const { return m_recursion_available; }
void set_recursion_available(bool b) { m_recursion_available = b; }
u16 question_count() const { return m_question_count; }
void set_question_count(u16 w) { m_question_count = w; }
u16 answer_count() const { return m_answer_count; }
void set_answer_count(u16 w) { m_answer_count = w; }
u16 authority_count() const { return m_authority_count; }
void set_authority_count(u16 w) { m_authority_count = w; }
u16 additional_count() const { return m_additional_count; }
void set_additional_count(u16 w) { m_additional_count = w; }
void* payload() { return this + 1; }
const void* payload() const { return this + 1; }
private:
NetworkOrdered<u16> m_id;
bool m_recursion_desired : 1;
bool m_truncated : 1;
bool m_authoritative_answer : 1;
u8 m_opcode : 4;
bool m_query_or_response : 1;
u8 m_response_code : 4;
bool m_checking_disabled : 1;
bool m_authenticated_data : 1;
bool m_zero : 1;
bool m_recursion_available : 1;
NetworkOrdered<u16> m_question_count;
NetworkOrdered<u16> m_answer_count;
NetworkOrdered<u16> m_authority_count;
NetworkOrdered<u16> m_additional_count;
};
static_assert(sizeof(DNSPacket) == 12);

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/String.h>
#include <AK/Types.h>
class DNSQuestion {
public:
DNSQuestion(const String& name, u16 record_type, u16 class_code)
: m_name(name)
, m_record_type(record_type)
, m_class_code(class_code)
{
}
u16 record_type() const { return m_record_type; }
u16 class_code() const { return m_class_code; }
const String& name() const { return m_name; }
bool operator==(const DNSQuestion& other) const
{
return m_name == other.m_name && m_record_type == other.m_record_type && m_class_code == other.m_class_code;
}
bool operator!=(const DNSQuestion& other) const
{
return !(*this == other);
}
private:
String m_name;
u16 m_record_type { 0 };
u16 m_class_code { 0 };
};

View file

@ -0,0 +1,98 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "DNSRequest.h"
#include "DNSPacket.h"
#include <AK/BufferStream.h>
#include <AK/StringBuilder.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <stdlib.h>
#define C_IN 1
DNSRequest::DNSRequest()
: m_id(arc4random_uniform(UINT16_MAX))
{
}
void DNSRequest::add_question(const String& name, u16 record_type, ShouldRandomizeCase should_randomize_case)
{
ASSERT(m_questions.size() <= UINT16_MAX);
if (name.is_empty())
return;
StringBuilder builder;
for (size_t i = 0; i < name.length(); ++i) {
u8 ch = name[i];
if (should_randomize_case == ShouldRandomizeCase::Yes) {
// Randomize the 0x20 bit in every ASCII character.
if (isalpha(ch)) {
if (arc4random_uniform(2))
ch |= 0x20;
else
ch &= ~0x20;
}
}
builder.append(ch);
}
if (name[name.length() - 1] != '.')
builder.append('.');
m_questions.empend(builder.to_string(), record_type, C_IN);
}
ByteBuffer DNSRequest::to_byte_buffer() const
{
DNSPacket request_header;
request_header.set_id(m_id);
request_header.set_is_query();
request_header.set_opcode(0);
request_header.set_truncated(false);
request_header.set_recursion_desired(true);
request_header.set_question_count(m_questions.size());
auto buffer = ByteBuffer::create_uninitialized(m_questions.size() * 4096);
BufferStream stream(buffer);
stream << ByteBuffer::wrap(&request_header, sizeof(request_header));
for (auto& question : m_questions) {
auto parts = question.name().split('.');
for (auto& part : parts) {
stream << (u8)part.length();
stream << part;
}
stream << '\0';
stream << htons(question.record_type());
stream << htons(question.class_code());
}
stream.snip();
return buffer;
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include "DNSQuestion.h"
#include <AK/Types.h>
#include <AK/Vector.h>
#define T_A 1
#define T_NS 2
#define T_CNAME 5
#define T_SOA 6
#define T_PTR 12
#define T_MX 15
enum class ShouldRandomizeCase {
No = 0,
Yes
};
class DNSRequest {
public:
DNSRequest();
void add_question(const String& name, u16 record_type, ShouldRandomizeCase);
const Vector<DNSQuestion>& questions() const { return m_questions; }
u16 question_count() const
{
ASSERT(m_questions.size() < UINT16_MAX);
return m_questions.size();
}
u16 id() const { return m_id; }
ByteBuffer to_byte_buffer() const;
private:
u16 m_id { 0 };
Vector<DNSQuestion> m_questions;
};

View file

@ -0,0 +1,147 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "DNSResponse.h"
#include "DNSPacket.h"
#include "DNSRequest.h"
#include <AK/IPv4Address.h>
#include <AK/StringBuilder.h>
static String parse_dns_name(const u8* data, size_t& offset, size_t max_offset, size_t recursion_level = 0);
class [[gnu::packed]] DNSRecordWithoutName
{
public:
DNSRecordWithoutName() {}
u16 type() const { return m_type; }
u16 record_class() const { return m_class; }
u32 ttl() const { return m_ttl; }
u16 data_length() const { return m_data_length; }
void* data() { return this + 1; }
const void* data() const { return this + 1; }
private:
NetworkOrdered<u16> m_type;
NetworkOrdered<u16> m_class;
NetworkOrdered<u32> m_ttl;
NetworkOrdered<u16> m_data_length;
};
static_assert(sizeof(DNSRecordWithoutName) == 10);
Optional<DNSResponse> DNSResponse::from_raw_response(const u8* raw_data, size_t raw_size)
{
if (raw_size < sizeof(DNSPacket)) {
dbg() << "DNS response not large enough (" << raw_size << " out of " << sizeof(DNSPacket) << ") to be a DNS packet.";
return {};
}
auto& response_header = *(const DNSPacket*)(raw_data);
dbgprintf("Got response (ID: %u)\n", response_header.id());
dbgprintf(" Question count: %u\n", response_header.question_count());
dbgprintf(" Answer count: %u\n", response_header.answer_count());
dbgprintf(" Authority count: %u\n", response_header.authority_count());
dbgprintf("Additional count: %u\n", response_header.additional_count());
DNSResponse response;
response.m_id = response_header.id();
response.m_code = response_header.response_code();
if (response.code() != DNSResponse::Code::NOERROR)
return response;
size_t offset = sizeof(DNSPacket);
for (u16 i = 0; i < response_header.question_count(); ++i) {
auto name = parse_dns_name(raw_data, offset, raw_size);
struct RawDNSAnswerQuestion {
NetworkOrdered<u16> record_type;
NetworkOrdered<u16> class_code;
};
auto& record_and_class = *(const RawDNSAnswerQuestion*)&raw_data[offset];
response.m_questions.empend(name, record_and_class.record_type, record_and_class.class_code);
auto& question = response.m_questions.last();
offset += 4;
dbg() << "Question #" << i << ": _" << question.name() << "_ type: " << question.record_type() << ", class: " << question.class_code();
}
for (u16 i = 0; i < response_header.answer_count(); ++i) {
auto name = parse_dns_name(raw_data, offset, raw_size);
auto& record = *(const DNSRecordWithoutName*)(&raw_data[offset]);
String data;
offset += sizeof(DNSRecordWithoutName);
if (record.type() == T_PTR) {
size_t dummy_offset = offset;
data = parse_dns_name(raw_data, dummy_offset, raw_size);
} else if (record.type() == T_A) {
auto ipv4_address = IPv4Address((const u8*)record.data());
data = ipv4_address.to_string();
} else {
// FIXME: Parse some other record types perhaps?
dbg() << " data=(unimplemented record type " << record.type() << ")";
}
dbg() << "Answer #" << i << ": name=_" << name << "_, type=" << record.type() << ", ttl=" << record.ttl() << ", length=" << record.data_length() << ", data=_" << data << "_";
response.m_answers.empend(name, record.type(), record.record_class(), record.ttl(), data);
offset += record.data_length();
}
return response;
}
String parse_dns_name(const u8* data, size_t& offset, size_t max_offset, size_t recursion_level)
{
if (recursion_level > 4)
return {};
Vector<char, 128> buf;
while (offset < max_offset) {
u8 ch = data[offset];
if (ch == '\0') {
++offset;
break;
}
if ((ch & 0xc0) == 0xc0) {
if ((offset + 1) >= max_offset)
return {};
size_t dummy = (ch & 0x3f) << 8 | data[offset + 1];
offset += 2;
StringBuilder builder;
builder.append(buf.data(), buf.size());
auto okay = parse_dns_name(data, dummy, max_offset, recursion_level + 1);
builder.append(okay);
return builder.to_string();
}
for (size_t i = 0; i < ch; ++i)
buf.append(data[offset + i + 1]);
buf.append('.');
offset += ch + 1;
}
return String::copy(buf);
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include "DNSAnswer.h"
#include "DNSQuestion.h"
#include <AK/Types.h>
#include <AK/Vector.h>
#include <AK/Optional.h>
class DNSResponse {
public:
static Optional<DNSResponse> from_raw_response(const u8*, size_t);
u16 id() const { return m_id; }
const Vector<DNSQuestion>& questions() const { return m_questions; }
const Vector<DNSAnswer>& answers() const { return m_answers; }
u16 question_count() const
{
ASSERT(m_questions.size() <= UINT16_MAX);
return m_questions.size();
}
u16 answer_count() const
{
ASSERT(m_answers.size() <= UINT16_MAX);
return m_answers.size();
}
enum class Code : u8 {
NOERROR = 0,
FORMERR = 1,
SERVFAIL = 2,
NXDOMAIN = 3,
NOTIMP = 4,
REFUSED = 5,
YXDOMAIN = 6,
XRRSET = 7,
NOTAUTH = 8,
NOTZONE = 9,
};
Code code() const { return (Code)m_code; }
private:
DNSResponse() {}
u16 m_id { 0 };
u8 m_code { 0 };
Vector<DNSQuestion> m_questions;
Vector<DNSAnswer> m_answers;
};

View file

@ -0,0 +1,259 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "LookupServer.h"
#include "DNSRequest.h"
#include "DNSResponse.h"
#include <AK/HashMap.h>
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <LibCore/ConfigFile.h>
#include <LibCore/EventLoop.h>
#include <LibCore/File.h>
#include <LibCore/LocalServer.h>
#include <LibCore/LocalSocket.h>
#include <LibCore/UDPSocket.h>
#include <stdio.h>
#include <unistd.h>
LookupServer::LookupServer()
{
auto config = Core::ConfigFile::get_for_system("LookupServer");
dbg() << "Using network config file at " << config->file_name();
m_nameserver = config->read_entry("DNS", "Nameserver", "1.1.1.1");
load_etc_hosts();
m_local_server = Core::LocalServer::construct(this);
m_local_server->on_ready_to_accept = [this]() {
auto socket = m_local_server->accept();
socket->on_ready_to_read = [this, socket]() {
service_client(socket);
RefPtr<Core::LocalSocket> keeper = socket;
const_cast<Core::LocalSocket&>(*socket).on_ready_to_read = [] {};
};
};
bool ok = m_local_server->take_over_from_system_server();
ASSERT(ok);
}
void LookupServer::load_etc_hosts()
{
auto file = Core::File::construct("/etc/hosts");
if (!file->open(Core::IODevice::ReadOnly))
return;
while (!file->eof()) {
auto line = file->read_line(1024);
if (line.is_empty())
break;
auto str_line = String((const char*)line.data(), line.size() - 1, Chomp);
auto fields = str_line.split('\t');
auto sections = fields[0].split('.');
IPv4Address addr {
(u8)atoi(sections[0].characters()),
(u8)atoi(sections[1].characters()),
(u8)atoi(sections[2].characters()),
(u8)atoi(sections[3].characters()),
};
auto name = fields[1];
m_etc_hosts.set(name, addr.to_string());
IPv4Address reverse_addr {
(u8)atoi(sections[3].characters()),
(u8)atoi(sections[2].characters()),
(u8)atoi(sections[1].characters()),
(u8)atoi(sections[0].characters()),
};
StringBuilder builder;
builder.append(reverse_addr.to_string());
builder.append(".in-addr.arpa");
m_etc_hosts.set(builder.to_string(), name);
}
}
void LookupServer::service_client(RefPtr<Core::LocalSocket> socket)
{
u8 client_buffer[1024];
int nrecv = socket->read(client_buffer, sizeof(client_buffer) - 1);
if (nrecv < 0) {
perror("read");
return;
}
client_buffer[nrecv] = '\0';
char lookup_type = client_buffer[0];
if (lookup_type != 'L' && lookup_type != 'R') {
dbg() << "Invalid lookup_type " << lookup_type;
return;
}
auto hostname = String((const char*)client_buffer + 1, nrecv - 1, Chomp);
dbg() << "Got request for '" << hostname << "' (using IP " << m_nameserver << ")";
Vector<String> responses;
if (auto known_host = m_etc_hosts.get(hostname); known_host.has_value()) {
responses.append(known_host.value());
} else if (!hostname.is_empty()) {
bool did_timeout;
int retries = 3;
do {
did_timeout = false;
if (lookup_type == 'L')
responses = lookup(hostname, did_timeout, T_A);
else if (lookup_type == 'R')
responses = lookup(hostname, did_timeout, T_PTR);
if (!did_timeout)
break;
} while (--retries);
if (did_timeout) {
fprintf(stderr, "LookupServer: Out of retries :(\n");
return;
}
}
if (responses.is_empty()) {
int nsent = socket->write("Not found.\n");
if (nsent < 0)
perror("write");
return;
}
for (auto& response : responses) {
auto line = String::format("%s\n", response.characters());
int nsent = socket->write(line);
if (nsent < 0) {
perror("write");
break;
}
}
}
Vector<String> LookupServer::lookup(const String& hostname, bool& did_timeout, unsigned short record_type, ShouldRandomizeCase should_randomize_case)
{
if (auto it = m_lookup_cache.find(hostname); it != m_lookup_cache.end()) {
auto& cached_lookup = it->value;
if (cached_lookup.question.record_type() == record_type) {
Vector<String> responses;
for (auto& cached_answer : cached_lookup.answers) {
dbg() << "Cache hit: " << hostname << " -> " << cached_answer.record_data() << ", expired: " << cached_answer.has_expired();
if (!cached_answer.has_expired()) {
responses.append(cached_answer.record_data());
}
}
if (!responses.is_empty())
return responses;
}
m_lookup_cache.remove(it);
}
DNSRequest request;
request.add_question(hostname, record_type, should_randomize_case);
auto buffer = request.to_byte_buffer();
auto udp_socket = Core::UDPSocket::construct();
udp_socket->set_blocking(true);
struct timeval timeout {
1, 0
};
int rc = setsockopt(udp_socket->fd(), SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
if (rc < 0) {
perror("setsockopt(SOL_SOCKET, SO_RCVTIMEO)");
return {};
}
if (!udp_socket->connect(m_nameserver, 53))
return {};
if (!udp_socket->write(buffer))
return {};
u8 response_buffer[4096];
int nrecv = udp_socket->read(response_buffer, sizeof(response_buffer));
if (nrecv == 0)
return {};
auto o_response = DNSResponse::from_raw_response(response_buffer, nrecv);
if (!o_response.has_value())
return {};
auto& response = o_response.value();
if (response.id() != request.id()) {
dbgprintf("LookupServer: ID mismatch (%u vs %u) :(\n", response.id(), request.id());
return {};
}
if (response.code() == DNSResponse::Code::REFUSED) {
if (should_randomize_case == ShouldRandomizeCase::Yes) {
// Retry with 0x20 case randomization turned off.
return lookup(hostname, did_timeout, record_type, ShouldRandomizeCase::No);
}
return {};
}
if (response.question_count() != request.question_count()) {
dbgprintf("LookupServer: Question count (%u vs %u) :(\n", response.question_count(), request.question_count());
return {};
}
for (size_t i = 0; i < request.question_count(); ++i) {
auto& request_question = request.questions()[i];
auto& response_question = response.questions()[i];
if (request_question != response_question) {
dbg() << "Request and response questions do not match";
dbg() << " Request: {_" << request_question.name() << "_, " << request_question.record_type() << ", " << request_question.class_code() << "}";
dbg() << " Response: {_" << response_question.name() << "_, " << response_question.record_type() << ", " << response_question.class_code() << "}";
return {};
}
}
if (response.answer_count() < 1) {
dbgprintf("LookupServer: Not enough answers (%u) :(\n", response.answer_count());
return {};
}
Vector<String, 8> responses;
Vector<DNSAnswer, 8> cacheable_answers;
for (auto& answer : response.answers()) {
if (answer.type() != T_A)
continue;
responses.append(answer.record_data());
if (!answer.has_expired())
cacheable_answers.append(answer);
}
if (!cacheable_answers.is_empty()) {
if (m_lookup_cache.size() >= 256)
m_lookup_cache.remove(m_lookup_cache.begin());
m_lookup_cache.set(hostname, { request.questions()[0], move(cacheable_answers) });
}
return responses;
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include "DNSRequest.h"
#include "DNSResponse.h"
#include <AK/HashMap.h>
#include <LibCore/Object.h>
class DNSAnswer;
class LookupServer final : public Core::Object {
C_OBJECT(LookupServer)
public:
LookupServer();
private:
void load_etc_hosts();
void service_client(RefPtr<Core::LocalSocket>);
Vector<String> lookup(const String& hostname, bool& did_timeout, unsigned short record_type, ShouldRandomizeCase = ShouldRandomizeCase::Yes);
struct CachedLookup {
DNSQuestion question;
Vector<DNSAnswer> answers;
};
RefPtr<Core::LocalServer> m_local_server;
String m_nameserver;
HashMap<String, String> m_etc_hosts;
HashMap<String, CachedLookup> m_lookup_cache;
};

View file

@ -0,0 +1,12 @@
OBJS = \
LookupServer.o \
DNSRequest.o \
DNSResponse.o \
DNSAnswer.o \
main.o
PROGRAM = LookupServer
LIB_DEPS = Core
include ../../Makefile.common

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "LookupServer.h"
#include <LibCore/EventLoop.h>
#include <LibCore/LocalServer.h>
#include <stdio.h>
int main(int argc, char** argv)
{
(void)argc;
(void)argv;
if (pledge("stdio accept unix inet cpath rpath fattr", nullptr) < 0) {
perror("pledge");
return 1;
}
Core::EventLoop event_loop;
LookupServer server;
if (pledge("stdio accept inet", nullptr) < 0) {
perror("pledge");
return 1;
}
unveil(nullptr, nullptr);
return event_loop.exec();
}

3
Services/Makefile Normal file
View file

@ -0,0 +1,3 @@
SUBDIRS := $(patsubst %/Makefile,%/,$(wildcard */Makefile))
include ../Makefile.subdir

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "ClientConnection.h"
#include "NotificationClientEndpoint.h"
#include "NotificationWindow.h"
#include <AK/HashMap.h>
namespace NotificationServer {
static HashMap<int, RefPtr<ClientConnection>> s_connections;
ClientConnection::ClientConnection(Core::LocalSocket& client_socket, int client_id)
: IPC::ClientConnection<NotificationServerEndpoint>(*this, client_socket, client_id)
{
s_connections.set(client_id, *this);
}
ClientConnection::~ClientConnection()
{
}
void ClientConnection::die()
{
s_connections.remove(client_id());
}
OwnPtr<Messages::NotificationServer::GreetResponse> ClientConnection::handle(const Messages::NotificationServer::Greet&)
{
return make<Messages::NotificationServer::GreetResponse>(client_id());
}
OwnPtr<Messages::NotificationServer::ShowNotificationResponse> ClientConnection::handle(const Messages::NotificationServer::ShowNotification& message)
{
auto window = NotificationWindow::construct(message.text(), message.title(), message.icon());
window->show();
return make<Messages::NotificationServer::ShowNotificationResponse>();
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <LibIPC/ClientConnection.h>
#include <NotificationServer/NotificationServerEndpoint.h>
namespace NotificationServer {
class ClientConnection final : public IPC::ClientConnection<NotificationServerEndpoint>
, public NotificationServerEndpoint {
C_OBJECT(ClientConnection)
public:
~ClientConnection() override;
virtual void die() override;
private:
explicit ClientConnection(Core::LocalSocket&, int client_id);
virtual OwnPtr<Messages::NotificationServer::GreetResponse> handle(const Messages::NotificationServer::Greet&) override;
virtual OwnPtr<Messages::NotificationServer::ShowNotificationResponse> handle(const Messages::NotificationServer::ShowNotification&) override;
};
}

View file

@ -0,0 +1,24 @@
OBJS = \
main.o \
ClientConnection.o \
NotificationWindow.o
PROGRAM = NotificationServer
LIB_DEPS = GUI Gfx Core IPC
EXTRA_CLEAN = NotificationServerEndpoint.h NotificationClientEndpoint.h
*.cpp: NotificationServerEndpoint.h NotificationClientEndpoint.h
NotificationServerEndpoint.h: NotificationServer.ipc | IPCCOMPILER
@echo "IPC $<"; $(IPCCOMPILER) $< > $@
NotificationClientEndpoint.h: NotificationClient.ipc | IPCCOMPILER
@echo "IPC $<"; $(IPCCOMPILER) $< > $@
install:
mkdir -p $(SERENITY_BASE_DIR)/Root/usr/include/NotificationServer/
cp *.h $(SERENITY_BASE_DIR)/Root/usr/include/NotificationServer/
include ../../Makefile.common

View file

@ -0,0 +1,4 @@
endpoint NotificationClient = 92
{
Dummy() =|
}

View file

@ -0,0 +1,7 @@
endpoint NotificationServer = 95
{
// Basic protocol
Greet() => (i32 client_id)
ShowNotification(String text, String title, Gfx::ShareableBitmap icon) => ()
}

View file

@ -0,0 +1,124 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "NotificationWindow.h"
#include <AK/Vector.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/Desktop.h>
#include <LibGUI/Label.h>
#include <LibGUI/Widget.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Font.h>
#include <LibGfx/ShareableBitmap.h>
namespace NotificationServer {
static Vector<RefPtr<NotificationWindow>> s_windows;
void update_notification_window_locations()
{
Gfx::Rect last_window_rect;
for (auto& window : s_windows) {
Gfx::Point new_window_location;
if (last_window_rect.is_null())
new_window_location = GUI::Desktop::the().rect().top_right().translated(-window->rect().width() - 24, 26);
else
new_window_location = last_window_rect.bottom_left().translated(0, 10);
if (window->rect().location() != new_window_location) {
window->move_to(new_window_location);
window->set_original_rect(window->rect());
}
last_window_rect = window->rect();
}
}
NotificationWindow::NotificationWindow(const String& text, const String& title, const Gfx::ShareableBitmap& icon)
{
s_windows.append(this);
set_window_type(GUI::WindowType::Notification);
set_resizable(false);
set_minimizable(false);
Gfx::Rect lowest_notification_rect_on_screen;
for (auto& window : s_windows) {
if (window->m_original_rect.y() > lowest_notification_rect_on_screen.y())
lowest_notification_rect_on_screen = window->m_original_rect;
}
Gfx::Rect rect;
rect.set_width(220);
rect.set_height(40);
rect.set_location(GUI::Desktop::the().rect().top_right().translated(-rect.width() - 24, 26));
if (!lowest_notification_rect_on_screen.is_null())
rect.set_location(lowest_notification_rect_on_screen.bottom_left().translated(0, 10));
set_rect(rect);
m_original_rect = rect;
auto& widget = set_main_widget<GUI::Widget>();
widget.set_fill_with_background_color(true);
widget.set_layout<GUI::HorizontalBoxLayout>();
widget.layout()->set_margins({ 8, 8, 8, 8 });
widget.layout()->set_spacing(6);
if (icon.is_valid()) {
auto& icon_label = widget.add<GUI::Label>();
icon_label.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed);
icon_label.set_preferred_size(32, 32);
icon_label.set_icon(icon.bitmap());
}
auto& left_container = widget.add<GUI::Widget>();
left_container.set_layout<GUI::VerticalBoxLayout>();
auto& title_label = left_container.add<GUI::Label>(title);
title_label.set_font(Gfx::Font::default_bold_font());
title_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
auto& text_label = left_container.add<GUI::Label>(text);
text_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
auto& right_container = widget.add<GUI::Widget>();
right_container.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill);
right_container.set_preferred_size(36, 0);
right_container.set_layout<GUI::HorizontalBoxLayout>();
on_close_request = [this] {
s_windows.remove_first_matching([this](auto& entry) { return entry == this; });
update_notification_window_locations();
return CloseRequestDecision::Close;
};
}
NotificationWindow::~NotificationWindow()
{
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <LibGUI/Window.h>
namespace NotificationServer {
class NotificationWindow final : public GUI::Window {
C_OBJECT(NotificationWindow);
public:
virtual ~NotificationWindow() override;
void set_original_rect(Gfx::Rect original_rect) { m_original_rect = original_rect; };
private:
NotificationWindow(const String& text, const String& title, const Gfx::ShareableBitmap&);
Gfx::Rect m_original_rect;
};
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "ClientConnection.h"
#include <LibCore/EventLoop.h>
#include <LibCore/LocalServer.h>
#include <LibGUI/Application.h>
#include <LibGUI/WindowServerConnection.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char** argv)
{
if (pledge("stdio shared_buffer accept rpath wpath cpath unix fattr", nullptr) < 0) {
perror("pledge");
return 1;
}
GUI::Application app(argc, argv);
auto server = Core::LocalServer::construct();
bool ok = server->take_over_from_system_server();
ASSERT(ok);
server->on_ready_to_accept = [&] {
auto client_socket = server->accept();
if (!client_socket) {
dbg() << "NotificationServer: accept failed.";
return;
}
static int s_next_client_id = 0;
int client_id = ++s_next_client_id;
IPC::new_client_connection<NotificationServer::ClientConnection>(*client_socket, client_id);
};
if (unveil("/res", "r") < 0) {
perror("unveil");
return 1;
}
unveil(nullptr, nullptr);
if (pledge("stdio shared_buffer accept rpath", nullptr) < 0) {
perror("pledge");
return 1;
}
return app.exec();
}

View file

@ -0,0 +1,92 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <AK/Badge.h>
#include <ProtocolServer/Download.h>
#include <ProtocolServer/PSClientConnection.h>
// FIXME: What about rollover?
static i32 s_next_id = 1;
static HashMap<i32, RefPtr<Download>>& all_downloads()
{
static HashMap<i32, RefPtr<Download>> map;
return map;
}
Download* Download::find_by_id(i32 id)
{
return const_cast<Download*>(all_downloads().get(id).value_or(nullptr));
}
Download::Download(PSClientConnection& client)
: m_id(s_next_id++)
, m_client(client.make_weak_ptr())
{
all_downloads().set(m_id, this);
}
Download::~Download()
{
}
void Download::stop()
{
all_downloads().remove(m_id);
}
void Download::set_payload(const ByteBuffer& payload)
{
m_payload = payload;
m_total_size = payload.size();
}
void Download::set_response_headers(const HashMap<String, String>& response_headers)
{
m_response_headers = response_headers;
}
void Download::did_finish(bool success)
{
if (!m_client) {
dbg() << "Download::did_finish() after the client already disconnected.";
return;
}
m_client->did_finish_download({}, *this, success);
all_downloads().remove(m_id);
}
void Download::did_progress(Optional<u32> total_size, u32 downloaded_size)
{
if (!m_client) {
// FIXME: We should also abort the download in this situation, I guess!
dbg() << "Download::did_progress() after the client already disconnected.";
return;
}
m_total_size = total_size;
m_downloaded_size = downloaded_size;
m_client->did_progress_download({}, *this);
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/HashMap.h>
#include <AK/Optional.h>
#include <AK/RefCounted.h>
#include <AK/URL.h>
#include <AK/WeakPtr.h>
class PSClientConnection;
class Download : public RefCounted<Download> {
public:
virtual ~Download();
static Download* find_by_id(i32);
i32 id() const { return m_id; }
URL url() const { return m_url; }
Optional<u32> total_size() const { return m_total_size; }
size_t downloaded_size() const { return m_downloaded_size; }
const ByteBuffer& payload() const { return m_payload; }
const HashMap<String, String>& response_headers() const { return m_response_headers; }
void stop();
protected:
explicit Download(PSClientConnection&);
void did_finish(bool success);
void did_progress(Optional<u32> total_size, u32 downloaded_size);
void set_payload(const ByteBuffer&);
void set_response_headers(const HashMap<String, String>&);
private:
i32 m_id;
URL m_url;
Optional<u32> m_total_size {};
size_t m_downloaded_size { 0 };
ByteBuffer m_payload;
HashMap<String, String> m_response_headers;
WeakPtr<PSClientConnection> m_client;
};

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <LibHTTP/HttpJob.h>
#include <LibHTTP/HttpResponse.h>
#include <ProtocolServer/HttpDownload.h>
HttpDownload::HttpDownload(PSClientConnection& client, NonnullRefPtr<HTTP::HttpJob>&& job)
: Download(client)
, m_job(job)
{
m_job->on_finish = [this](bool success) {
if (auto* response = m_job->response()) {
set_payload(response->payload());
set_response_headers(response->headers());
}
// if we didn't know the total size, pretend that the download finished successfully
// and set the total size to the downloaded size
if (!total_size().has_value())
did_progress(downloaded_size(), downloaded_size());
did_finish(success);
};
m_job->on_progress = [this](Optional<u32> total, u32 current) {
did_progress(total, current);
};
}
HttpDownload::~HttpDownload()
{
}
NonnullRefPtr<HttpDownload> HttpDownload::create_with_job(Badge<HttpProtocol>, PSClientConnection& client, NonnullRefPtr<HTTP::HttpJob>&& job)
{
return adopt(*new HttpDownload(client, move(job)));
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/Badge.h>
#include <LibCore/Forward.h>
#include <LibHTTP/HttpJob.h>
#include <ProtocolServer/Download.h>
class HttpProtocol;
class HttpDownload final : public Download {
public:
virtual ~HttpDownload() override;
static NonnullRefPtr<HttpDownload> create_with_job(Badge<HttpProtocol>, PSClientConnection&, NonnullRefPtr<HTTP::HttpJob>&&);
private:
explicit HttpDownload(PSClientConnection&, NonnullRefPtr<HTTP::HttpJob>&&);
NonnullRefPtr<HTTP::HttpJob> m_job;
};

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <LibHTTP/HttpJob.h>
#include <LibHTTP/HttpRequest.h>
#include <ProtocolServer/HttpDownload.h>
#include <ProtocolServer/HttpProtocol.h>
HttpProtocol::HttpProtocol()
: Protocol("http")
{
}
HttpProtocol::~HttpProtocol()
{
}
RefPtr<Download> HttpProtocol::start_download(PSClientConnection& client, const URL& url)
{
HTTP::HttpRequest request;
request.set_method(HTTP::HttpRequest::Method::GET);
request.set_url(url);
auto job = request.schedule();
if (!job)
return nullptr;
return HttpDownload::create_with_job({}, client, (HTTP::HttpJob&)*job);
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <ProtocolServer/Protocol.h>
class HttpProtocol final : public Protocol {
public:
HttpProtocol();
virtual ~HttpProtocol() override;
virtual RefPtr<Download> start_download(PSClientConnection&, const URL&) override;
};

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <LibHTTP/HttpResponse.h>
#include <LibHTTP/HttpsJob.h>
#include <ProtocolServer/HttpsDownload.h>
HttpsDownload::HttpsDownload(PSClientConnection& client, NonnullRefPtr<HTTP::HttpsJob>&& job)
: Download(client)
, m_job(job)
{
m_job->on_finish = [this](bool success) {
if (auto* response = m_job->response()) {
set_payload(response->payload());
set_response_headers(response->headers());
}
// if we didn't know the total size, pretend that the download finished successfully
// and set the total size to the downloaded size
if (!total_size().has_value())
did_progress(downloaded_size(), downloaded_size());
did_finish(success);
};
m_job->on_progress = [this](Optional<u32> total, u32 current) {
did_progress(total, current);
};
}
HttpsDownload::~HttpsDownload()
{
}
NonnullRefPtr<HttpsDownload> HttpsDownload::create_with_job(Badge<HttpsProtocol>, PSClientConnection& client, NonnullRefPtr<HTTP::HttpsJob>&& job)
{
return adopt(*new HttpsDownload(client, move(job)));
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2020, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/Badge.h>
#include <LibCore/Forward.h>
#include <LibHTTP/HttpsJob.h>
#include <ProtocolServer/Download.h>
class HttpsProtocol;
class HttpsDownload final : public Download {
public:
virtual ~HttpsDownload() override;
static NonnullRefPtr<HttpsDownload> create_with_job(Badge<HttpsProtocol>, PSClientConnection&, NonnullRefPtr<HTTP::HttpsJob>&&);
private:
explicit HttpsDownload(PSClientConnection&, NonnullRefPtr<HTTP::HttpsJob>&&);
NonnullRefPtr<HTTP::HttpsJob> m_job;
};

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2018-2020, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <LibHTTP/HttpRequest.h>
#include <LibHTTP/HttpsJob.h>
#include <ProtocolServer/HttpsDownload.h>
#include <ProtocolServer/HttpsProtocol.h>
HttpsProtocol::HttpsProtocol()
: Protocol("https")
{
}
HttpsProtocol::~HttpsProtocol()
{
}
RefPtr<Download> HttpsProtocol::start_download(PSClientConnection& client, const URL& url)
{
HTTP::HttpRequest request;
request.set_method(HTTP::HttpRequest::Method::GET);
request.set_url(url);
auto job = HTTP::HttpsJob::construct(request);
auto download = HttpsDownload::create_with_job({}, client, (HTTP::HttpsJob&)*job);
job->start();
return download;
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2018-2020, The SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <ProtocolServer/Protocol.h>
class HttpsProtocol final : public Protocol {
public:
HttpsProtocol();
virtual ~HttpsProtocol() override;
virtual RefPtr<Download> start_download(PSClientConnection&, const URL&) override;
};

View file

@ -0,0 +1,25 @@
OBJS = \
PSClientConnection.o \
Protocol.o \
Download.o \
HttpProtocol.o \
HttpDownload.o \
HttpsProtocol.o \
HttpsDownload.o \
main.o
PROGRAM = ProtocolServer
LIB_DEPS = HTTP TLS Crypto Core IPC
EXTRA_CLEAN = ProtocolServerEndpoint.h ProtocolClientEndpoint.h
*.cpp: ProtocolServerEndpoint.h ProtocolClientEndpoint.h
ProtocolServerEndpoint.h: ProtocolServer.ipc | IPCCOMPILER
@echo "IPC $<"; $(IPCCOMPILER) $< > $@
ProtocolClientEndpoint.h: ProtocolClient.ipc | IPCCOMPILER
@echo "IPC $<"; $(IPCCOMPILER) $< > $@
include ../../Makefile.common

View file

@ -0,0 +1,112 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <AK/Badge.h>
#include <AK/SharedBuffer.h>
#include <ProtocolServer/Download.h>
#include <ProtocolServer/PSClientConnection.h>
#include <ProtocolServer/Protocol.h>
#include <ProtocolServer/ProtocolClientEndpoint.h>
static HashMap<int, RefPtr<PSClientConnection>> s_connections;
PSClientConnection::PSClientConnection(Core::LocalSocket& socket, int client_id)
: IPC::ClientConnection<ProtocolServerEndpoint>(*this, socket, client_id)
{
s_connections.set(client_id, *this);
}
PSClientConnection::~PSClientConnection()
{
}
void PSClientConnection::die()
{
s_connections.remove(client_id());
}
OwnPtr<Messages::ProtocolServer::IsSupportedProtocolResponse> PSClientConnection::handle(const Messages::ProtocolServer::IsSupportedProtocol& message)
{
bool supported = Protocol::find_by_name(message.protocol().to_lowercase());
return make<Messages::ProtocolServer::IsSupportedProtocolResponse>(supported);
}
OwnPtr<Messages::ProtocolServer::StartDownloadResponse> PSClientConnection::handle(const Messages::ProtocolServer::StartDownload& message)
{
URL url(message.url());
if (!url.is_valid())
return make<Messages::ProtocolServer::StartDownloadResponse>(-1);
auto* protocol = Protocol::find_by_name(url.protocol());
if (!protocol)
return make<Messages::ProtocolServer::StartDownloadResponse>(-1);
auto download = protocol->start_download(*this, url);
return make<Messages::ProtocolServer::StartDownloadResponse>(download->id());
}
OwnPtr<Messages::ProtocolServer::StopDownloadResponse> PSClientConnection::handle(const Messages::ProtocolServer::StopDownload& message)
{
auto* download = Download::find_by_id(message.download_id());
bool success = false;
if (download) {
download->stop();
success = true;
}
return make<Messages::ProtocolServer::StopDownloadResponse>(success);
}
void PSClientConnection::did_finish_download(Badge<Download>, Download& download, bool success)
{
RefPtr<SharedBuffer> buffer;
if (success && download.payload().size() > 0 && !download.payload().is_null()) {
buffer = SharedBuffer::create_with_size(download.payload().size());
memcpy(buffer->data(), download.payload().data(), download.payload().size());
buffer->seal();
buffer->share_with(client_pid());
m_shared_buffers.set(buffer->shbuf_id(), buffer);
}
ASSERT(download.total_size().has_value());
IPC::Dictionary response_headers;
for (auto& it : download.response_headers())
response_headers.add(it.key, it.value);
post_message(Messages::ProtocolClient::DownloadFinished(download.id(), success, download.total_size().value(), buffer ? buffer->shbuf_id() : -1, response_headers));
}
void PSClientConnection::did_progress_download(Badge<Download>, Download& download)
{
post_message(Messages::ProtocolClient::DownloadProgress(download.id(), download.total_size(), download.downloaded_size()));
}
OwnPtr<Messages::ProtocolServer::GreetResponse> PSClientConnection::handle(const Messages::ProtocolServer::Greet&)
{
return make<Messages::ProtocolServer::GreetResponse>(client_id());
}
OwnPtr<Messages::ProtocolServer::DisownSharedBufferResponse> PSClientConnection::handle(const Messages::ProtocolServer::DisownSharedBuffer& message)
{
m_shared_buffers.remove(message.shbuf_id());
return make<Messages::ProtocolServer::DisownSharedBufferResponse>();
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/HashMap.h>
#include <LibIPC/ClientConnection.h>
#include <ProtocolServer/ProtocolServerEndpoint.h>
class Download;
class PSClientConnection final : public IPC::ClientConnection<ProtocolServerEndpoint>
, public ProtocolServerEndpoint {
C_OBJECT(PSClientConnection)
public:
explicit PSClientConnection(Core::LocalSocket&, int client_id);
~PSClientConnection() override;
virtual void die() override;
void did_finish_download(Badge<Download>, Download&, bool success);
void did_progress_download(Badge<Download>, Download&);
private:
virtual OwnPtr<Messages::ProtocolServer::GreetResponse> handle(const Messages::ProtocolServer::Greet&) override;
virtual OwnPtr<Messages::ProtocolServer::IsSupportedProtocolResponse> handle(const Messages::ProtocolServer::IsSupportedProtocol&) override;
virtual OwnPtr<Messages::ProtocolServer::StartDownloadResponse> handle(const Messages::ProtocolServer::StartDownload&) override;
virtual OwnPtr<Messages::ProtocolServer::StopDownloadResponse> handle(const Messages::ProtocolServer::StopDownload&) override;
virtual OwnPtr<Messages::ProtocolServer::DisownSharedBufferResponse> handle(const Messages::ProtocolServer::DisownSharedBuffer&) override;
HashMap<i32, RefPtr<AK::SharedBuffer>> m_shared_buffers;
};

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <AK/HashMap.h>
#include <ProtocolServer/Protocol.h>
static HashMap<String, Protocol*>& all_protocols()
{
static HashMap<String, Protocol*> map;
return map;
}
Protocol* Protocol::find_by_name(const String& name)
{
return all_protocols().get(name).value_or(nullptr);
}
Protocol::Protocol(const String& name)
{
all_protocols().set(name, this);
}
Protocol::~Protocol()
{
ASSERT_NOT_REACHED();
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/RefPtr.h>
#include <AK/URL.h>
class Download;
class PSClientConnection;
class Protocol {
public:
virtual ~Protocol();
const String& name() const { return m_name; }
virtual RefPtr<Download> start_download(PSClientConnection&, const URL&) = 0;
static Protocol* find_by_name(const String&);
protected:
explicit Protocol(const String& name);
private:
String m_name;
};

View file

@ -0,0 +1,6 @@
endpoint ProtocolClient = 13
{
// Download notifications
DownloadProgress(i32 download_id, Optional<u32> total_size, u32 downloaded_size) =|
DownloadFinished(i32 download_id, bool success, u32 total_size, i32 shbuf_id, IPC::Dictionary response_headers) =|
}

View file

@ -0,0 +1,15 @@
endpoint ProtocolServer = 9
{
// Basic protocol
Greet() => (i32 client_id)
// FIXME: It would be nice if the kernel provided a way to avoid this
DisownSharedBuffer(i32 shbuf_id) => ()
// Test if a specific protocol is supported, e.g "http"
IsSupportedProtocol(String protocol) => (bool supported)
// Download API
StartDownload(String url) => (i32 download_id)
StopDownload(i32 download_id) => (bool success)
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <LibCore/EventLoop.h>
#include <LibCore/LocalServer.h>
#include <LibIPC/ClientConnection.h>
#include <ProtocolServer/HttpProtocol.h>
#include <ProtocolServer/HttpsProtocol.h>
#include <ProtocolServer/PSClientConnection.h>
int main(int, char**)
{
if (pledge("stdio inet shared_buffer accept unix rpath cpath fattr", nullptr) < 0) {
perror("pledge");
return 1;
}
Core::EventLoop event_loop;
// FIXME: Establish a connection to LookupServer and then drop "unix"?
if (pledge("stdio inet shared_buffer accept unix", nullptr) < 0) {
perror("pledge");
return 1;
}
if (unveil("/tmp/portal/lookup", "rw") < 0) {
perror("unveil");
return 1;
}
if (unveil(nullptr, nullptr) < 0) {
perror("unveil");
return 1;
}
(void)*new HttpProtocol;
(void)*new HttpsProtocol;
auto server = Core::LocalServer::construct();
bool ok = server->take_over_from_system_server();
ASSERT(ok);
server->on_ready_to_accept = [&] {
auto client_socket = server->accept();
if (!client_socket) {
dbg() << "ProtocolServer: accept failed.";
return;
}
static int s_next_client_id = 0;
int client_id = ++s_next_client_id;
IPC::new_client_connection<PSClientConnection>(*client_socket, client_id);
};
return event_loop.exec();
}

View file

@ -0,0 +1,13 @@
OBJS = \
Service.o \
main.o
PROGRAM = SystemServer
LIB_DEPS = Core
install:
mkdir -p ../../Root/usr/include/SystemServer/
cp *.h ../../Root/usr/include/SystemServer/
include ../../Makefile.common

View file

@ -0,0 +1,365 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "Service.h"
#include <AK/HashMap.h>
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <LibCore/ConfigFile.h>
#include <LibCore/LocalSocket.h>
#include <fcntl.h>
#include <grp.h>
#include <libgen.h>
#include <pwd.h>
#include <sched.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
struct UidAndGids {
uid_t uid;
gid_t gid;
Vector<gid_t> extra_gids;
};
static HashMap<String, UidAndGids>* s_user_map;
static HashMap<pid_t, Service*> s_service_map;
void Service::resolve_user()
{
if (s_user_map == nullptr) {
s_user_map = new HashMap<String, UidAndGids>;
for (struct passwd* passwd = getpwent(); passwd; passwd = getpwent()) {
Vector<gid_t> extra_gids;
for (struct group* group = getgrent(); group; group = getgrent()) {
for (size_t m = 0; group->gr_mem[m]; ++m) {
if (!strcmp(group->gr_mem[m], passwd->pw_name))
extra_gids.append(group->gr_gid);
}
}
endgrent();
s_user_map->set(passwd->pw_name, { passwd->pw_uid, passwd->pw_gid, move(extra_gids) });
}
endpwent();
}
auto user = s_user_map->get(m_user);
if (!user.has_value()) {
dbg() << "Failed to resolve user name " << m_user;
ASSERT_NOT_REACHED();
}
m_uid = user.value().uid;
m_gid = user.value().gid;
m_extra_gids = user.value().extra_gids;
}
Service* Service::find_by_pid(pid_t pid)
{
auto it = s_service_map.find(pid);
if (it == s_service_map.end())
return nullptr;
return (*it).value;
}
static int ensure_parent_directories(const char* path)
{
ASSERT(path[0] == '/');
char* parent_buffer = strdup(path);
const char* parent = dirname(parent_buffer);
int rc = 0;
while (true) {
int rc = mkdir(parent, 0755);
if (rc == 0)
break;
if (errno != ENOENT)
break;
ensure_parent_directories(parent);
};
free(parent_buffer);
return rc;
}
void Service::setup_socket()
{
ASSERT(!m_socket_path.is_null());
ASSERT(m_socket_fd == -1);
ensure_parent_directories(m_socket_path.characters());
// Note: we use SOCK_CLOEXEC here to make sure we don't leak every socket to
// all the clients. We'll make the one we do need to pass down !CLOEXEC later
// after forking off the process.
m_socket_fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
if (m_socket_fd < 0) {
perror("socket");
ASSERT_NOT_REACHED();
}
if (fchown(m_socket_fd, m_uid, m_gid) < 0) {
perror("fchown");
ASSERT_NOT_REACHED();
}
if (fchmod(m_socket_fd, m_socket_permissions) < 0) {
perror("fchmod");
ASSERT_NOT_REACHED();
}
auto socket_address = Core::SocketAddress::local(m_socket_path);
auto un = socket_address.to_sockaddr_un();
int rc = bind(m_socket_fd, (const sockaddr*)&un, sizeof(un));
if (rc < 0) {
perror("bind");
ASSERT_NOT_REACHED();
}
rc = listen(m_socket_fd, 16);
if (rc < 0) {
perror("listen");
ASSERT_NOT_REACHED();
}
}
void Service::setup_notifier()
{
ASSERT(m_lazy);
ASSERT(m_socket_fd >= 0);
ASSERT(!m_socket_notifier);
m_socket_notifier = Core::Notifier::construct(m_socket_fd, Core::Notifier::Event::Read, this);
m_socket_notifier->on_ready_to_read = [this] {
dbg() << "Ready to read on behalf of " << name();
remove_child(*m_socket_notifier);
m_socket_notifier = nullptr;
spawn();
};
}
void Service::activate()
{
ASSERT(m_pid < 0);
if (m_lazy)
setup_notifier();
else
spawn();
}
void Service::spawn()
{
dbg() << "Spawning " << name();
m_run_timer.start();
m_pid = fork();
if (m_pid < 0) {
perror("fork");
ASSERT_NOT_REACHED();
} else if (m_pid == 0) {
// We are the child.
if (!m_working_directory.is_null()) {
if (chdir(m_working_directory.characters()) < 0) {
perror("chdir");
ASSERT_NOT_REACHED();
}
}
struct sched_param p;
p.sched_priority = m_priority;
int rc = sched_setparam(0, &p);
if (rc < 0) {
perror("sched_setparam");
ASSERT_NOT_REACHED();
}
if (!m_stdio_file_path.is_null()) {
close(STDIN_FILENO);
int fd = open_with_path_length(m_stdio_file_path.characters(), m_stdio_file_path.length(), O_RDWR, 0);
ASSERT(fd <= 0);
if (fd < 0) {
perror("open");
ASSERT_NOT_REACHED();
}
dup2(STDIN_FILENO, STDOUT_FILENO);
dup2(STDIN_FILENO, STDERR_FILENO);
if (isatty(STDIN_FILENO)) {
ioctl(STDIN_FILENO, TIOCSCTTY);
}
} else {
if (isatty(STDIN_FILENO)) {
ioctl(STDIN_FILENO, TIOCNOTTY);
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
int fd = open("/dev/null", O_RDWR);
ASSERT(fd == STDIN_FILENO);
dup2(STDIN_FILENO, STDOUT_FILENO);
dup2(STDIN_FILENO, STDERR_FILENO);
}
if (!m_socket_path.is_null()) {
ASSERT(m_socket_fd > 2);
dup2(m_socket_fd, 3);
// The new descriptor is !CLOEXEC here.
// This is true even if m_socket_fd == 3.
setenv("SOCKET_TAKEOVER", "1", true);
}
if (!m_user.is_null()) {
if (setgid(m_gid) < 0 || setgroups(m_extra_gids.size(), m_extra_gids.data()) < 0 || setuid(m_uid) < 0) {
dbgprintf("Failed to drop privileges (GID=%u, UID=%u)\n", m_gid, m_uid);
exit(1);
}
}
char* argv[m_extra_arguments.size() + 2];
argv[0] = const_cast<char*>(m_executable_path.characters());
for (size_t i = 0; i < m_extra_arguments.size(); i++)
argv[i + 1] = const_cast<char*>(m_extra_arguments[i].characters());
argv[m_extra_arguments.size() + 1] = nullptr;
rc = execv(argv[0], argv);
perror("exec");
ASSERT_NOT_REACHED();
} else {
// We are the parent.
s_service_map.set(m_pid, this);
}
}
void Service::did_exit(int exit_code)
{
ASSERT(m_pid > 0);
dbg() << "Service " << name() << " has exited with exit code " << exit_code;
s_service_map.remove(m_pid);
m_pid = -1;
if (!m_keep_alive)
return;
int run_time_in_msec = m_run_timer.elapsed();
bool exited_successfully = exit_code == 0;
if (!exited_successfully && run_time_in_msec < 1000) {
switch (m_restart_attempts) {
case 0:
dbg() << "Trying again";
break;
case 1:
dbg() << "Third time's a charm?";
break;
default:
dbg() << "Giving up on " << name() << ". Good luck!";
return;
}
m_restart_attempts++;
}
activate();
}
Service::Service(const Core::ConfigFile& config, const StringView& name)
: Core::Object(nullptr)
{
ASSERT(config.has_group(name));
set_name(name);
m_executable_path = config.read_entry(name, "Executable", String::format("/bin/%s", this->name().characters()));
m_extra_arguments = config.read_entry(name, "Arguments", "").split(' ');
m_stdio_file_path = config.read_entry(name, "StdIO");
String prio = config.read_entry(name, "Priority");
if (prio == "low")
m_priority = 10;
else if (prio == "normal" || prio.is_null())
m_priority = 30;
else if (prio == "high")
m_priority = 50;
else
ASSERT_NOT_REACHED();
m_keep_alive = config.read_bool_entry(name, "KeepAlive");
m_lazy = config.read_bool_entry(name, "Lazy");
m_user = config.read_entry(name, "User");
if (!m_user.is_null())
resolve_user();
m_socket_path = config.read_entry(name, "Socket");
if (!m_socket_path.is_null()) {
auto socket_permissions_string = config.read_entry(name, "SocketPermissions", "0600");
m_socket_permissions = strtol(socket_permissions_string.characters(), nullptr, 8) & 04777;
setup_socket();
}
m_working_directory = config.read_entry(name, "WorkingDirectory");
}
void Service::save_to(JsonObject& json)
{
Core::Object::save_to(json);
json.set("executable_path", m_executable_path);
// FIXME: This crashes Inspector.
/*
JsonArray extra_args;
for (String& arg : m_extra_arguments)
extra_args.append(arg);
json.set("extra_arguments", move(extra_args));
*/
json.set("stdio_file_path", m_stdio_file_path);
json.set("priority", m_priority);
json.set("keep_alive", m_keep_alive);
json.set("socket_path", m_socket_path);
json.set("socket_permissions", m_socket_permissions);
json.set("lazy", m_lazy);
json.set("user", m_user);
json.set("uid", m_uid);
json.set("gid", m_gid);
if (m_pid > 0)
json.set("pid", m_pid);
else
json.set("pid", nullptr);
json.set("restart_attempts", m_restart_attempts);
json.set("working_directory", m_working_directory);
}

View file

@ -0,0 +1,90 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/RefPtr.h>
#include <AK/String.h>
#include <LibCore/ElapsedTimer.h>
#include <LibCore/Notifier.h>
#include <LibCore/Object.h>
class Service final : public Core::Object {
C_OBJECT(Service)
public:
void activate();
void did_exit(int exit_code);
static Service* find_by_pid(pid_t);
void save_to(AK::JsonObject&) override;
private:
Service(const Core::ConfigFile&, const StringView& name);
void spawn();
// Path to the executable. By default this is /bin/{m_name}.
String m_executable_path;
// Extra arguments, starting from argv[1], to pass when exec'ing.
Vector<String> m_extra_arguments;
// File path to open as stdio fds.
String m_stdio_file_path;
int m_priority { 1 };
// Whether we should re-launch it if it exits.
bool m_keep_alive { false };
// Path to the socket to create and listen on on behalf of this service.
String m_socket_path;
// File system permissions for the socket.
mode_t m_socket_permissions { 0 };
// Whether we should only spawn this service once somebody connects to the socket.
bool m_lazy;
// The name of the user we should run this service as.
String m_user;
uid_t m_uid { 0 };
gid_t m_gid { 0 };
Vector<gid_t> m_extra_gids;
// PID of the running instance of this service.
pid_t m_pid { -1 };
// An open fd to the socket.
int m_socket_fd { -1 };
RefPtr<Core::Notifier> m_socket_notifier;
// Timer since we last spawned the service.
Core::ElapsedTimer m_run_timer;
// How many times we have tried to restart this service, only counting those
// times where it has exited unsuccessfully and too quickly.
int m_restart_attempts { 0 };
// The working directory in which to spawn the service
String m_working_directory;
void resolve_user();
void setup_socket();
void setup_notifier();
};

View file

@ -0,0 +1,139 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "Service.h"
#include <AK/Assertions.h>
#include <AK/ByteBuffer.h>
#include <LibCore/ConfigFile.h>
#include <LibCore/Event.h>
#include <LibCore/EventLoop.h>
#include <LibCore/File.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
static void sigchld_handler(int)
{
int status = 0;
pid_t pid = waitpid(-1, &status, WNOHANG);
if (!pid)
return;
dbg() << "Reaped child with pid " << pid;
Service* service = Service::find_by_pid(pid);
if (service == nullptr) {
dbg() << "There was no service with this pid, what is going on?";
return;
}
// Call service->did_exit(status) some time soon.
// We wouldn't want to run the complex logic, such
// as possibly spawning the service again, from the
// signal handler, so defer it.
Core::EventLoop::main().post_event(*service, make<Core::DeferredInvocationEvent>([=](auto&) {
service->did_exit(status);
}));
Core::EventLoop::wake();
}
static void check_for_test_mode()
{
auto f = Core::File::construct("/proc/cmdline");
if (!f->open(Core::IODevice::ReadOnly)) {
dbg() << "Failed to read command line: " << f->error_string();
ASSERT(false);
}
const String cmd = String::copy(f->read_all());
dbg() << "Read command line: " << cmd;
if (cmd.matches("*testmode=1*")) {
// Eventually, we should run a test binary and wait for it to finish
// before shutting down. But this is good enough for now.
dbg() << "Waiting for testmode shutdown...";
sleep(5);
dbg() << "Shutting down due to testmode...";
if (fork() == 0) {
execl("/bin/shutdown", "/bin/shutdown", "-n", nullptr);
ASSERT_NOT_REACHED();
}
} else {
dbg() << "Continuing normally";
}
}
static void mount_all_filesystems()
{
dbg() << "Spawning mount -a to mount all filesystems.";
pid_t pid = fork();
if (pid < 0) {
perror("fork");
ASSERT_NOT_REACHED();
} else if (pid == 0) {
execl("/bin/mount", "mount", "-a", nullptr);
perror("exec");
ASSERT_NOT_REACHED();
} else {
wait(nullptr);
}
}
int main(int, char**)
{
if (pledge("stdio proc exec tty accept unix rpath wpath cpath chown fattr id", nullptr) < 0) {
perror("pledge");
return 1;
}
mount_all_filesystems();
struct sigaction sa = {
.sa_handler = sigchld_handler,
.sa_mask = 0,
.sa_flags = SA_RESTART
};
sigaction(SIGCHLD, &sa, nullptr);
Core::EventLoop event_loop;
// Read our config and instantiate services.
// This takes care of setting up sockets.
NonnullRefPtrVector<Service> services;
auto config = Core::ConfigFile::get_for_system("SystemServer");
for (auto name : config->groups())
services.append(Service::construct(*config, name));
// After we've set them all up, activate them!
for (auto& service : services)
service.activate();
// This won't return if we're in test mode.
check_for_test_mode();
return event_loop.exec();
}

View file

@ -0,0 +1,6 @@
OBJS = \
main.o
PROGRAM = TTYServer
include ../../Makefile.common

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <AK/Assertions.h>
#include <AK/LogStream.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char** argv)
{
if (pledge("stdio tty proc exec", nullptr) < 0) {
perror("pledge");
return 1;
}
if (unveil("/bin/Shell", "x") < 0) {
perror("unveil");
return 1;
}
unveil(nullptr, nullptr);
if (argc < 2)
return -1;
dbgprintf("Starting console server on %s\n", argv[1]);
while (true) {
dbgprintf("Running shell on %s\n", argv[1]);
auto child = fork();
if (!child) {
int rc = execl("/bin/Shell", "Shell", nullptr);
ASSERT(rc < 0);
perror("execl");
exit(127);
} else {
int wstatus;
waitpid(child, &wstatus, 0);
dbgprintf("Shell on %s exited with code %d\n", argv[1], WEXITSTATUS(wstatus));
}
}
}

View file

@ -0,0 +1,188 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "Client.h"
#include <AK/BufferStream.h>
#include <AK/ByteBuffer.h>
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#include <AK/Types.h>
#include <LibCore/Notifier.h>
#include <LibCore/TCPSocket.h>
#include <stdio.h>
#include <unistd.h>
Client::Client(int id, RefPtr<Core::TCPSocket> socket, int ptm_fd)
: m_id(id)
, m_socket(move(socket))
, m_ptm_fd(ptm_fd)
, m_ptm_notifier(Core::Notifier::construct(ptm_fd, Core::Notifier::Read))
{
m_socket->on_ready_to_read = [this] { drain_socket(); };
m_ptm_notifier->on_ready_to_read = [this] { drain_pty(); };
m_parser.on_command = [this](const Command& command) { handle_command(command); };
m_parser.on_data = [this](const StringView& data) { handle_data(data); };
m_parser.on_error = [this]() { handle_error(); };
send_commands({
{ CMD_WILL, SUB_SUPPRESS_GO_AHEAD },
{ CMD_WILL, SUB_ECHO },
{ CMD_DO, SUB_SUPPRESS_GO_AHEAD },
{ CMD_DONT, SUB_ECHO },
});
}
void Client::drain_socket()
{
NonnullRefPtr<Client> protect(*this);
while (m_socket->can_read()) {
auto buf = m_socket->read(1024);
m_parser.write(buf);
if (m_socket->eof()) {
quit();
break;
}
}
}
void Client::drain_pty()
{
u8 buffer[BUFSIZ];
ssize_t nread = read(m_ptm_fd, buffer, sizeof(buffer));
if (nread < 0) {
perror("read(ptm)");
quit();
return;
}
if (nread == 0) {
quit();
return;
}
send_data(StringView(buffer, (size_t)nread));
}
void Client::handle_data(const StringView& data)
{
write(m_ptm_fd, data.characters_without_null_termination(), data.length());
}
void Client::handle_command(const Command& command)
{
switch (command.command) {
case CMD_DO:
// no response - we've already advertised our options, and none of
// them can be disabled (or re-enabled) after connecting.
break;
case CMD_DONT:
// no response - we only "support" two options (echo and suppres
// go-ahead), and both of them are always enabled.
break;
case CMD_WILL:
switch (command.subcommand) {
case SUB_ECHO:
// we always want to be the ones in control of the output. tell
// the client to disable local echo.
send_command({ CMD_DONT, SUB_ECHO });
break;
case SUB_SUPPRESS_GO_AHEAD:
send_command({ CMD_DO, SUB_SUPPRESS_GO_AHEAD });
break;
default:
// don't respond to unknown commands
break;
}
break;
case CMD_WONT:
// no response - we don't care about anything the client says they
// won't do.
break;
}
}
void Client::handle_error()
{
quit();
}
void Client::send_data(StringView data)
{
bool fast = true;
for (size_t i = 0; i < data.length(); i++) {
u8 c = data[i];
if (c == '\n' || c == 0xff)
fast = false;
}
if (fast) {
m_socket->write(data);
return;
}
StringBuilder builder;
for (size_t i = 0; i < data.length(); i++) {
u8 c = data[i];
switch (c) {
case '\n':
builder.append("\r\n");
break;
case IAC:
builder.append("\xff\xff");
break;
default:
builder.append(c);
break;
}
}
m_socket->write(builder.to_string());
}
void Client::send_command(Command command)
{
send_commands({ command });
}
void Client::send_commands(Vector<Command> commands)
{
auto buffer = ByteBuffer::create_uninitialized(commands.size() * 3);
BufferStream stream(buffer);
for (auto& command : commands)
stream << (u8)IAC << command.command << command.subcommand;
stream.snip();
m_socket->write(buffer.data(), buffer.size());
}
void Client::quit()
{
m_ptm_notifier->set_enabled(false);
close(m_ptm_fd);
m_socket->close();
if (on_exit)
on_exit();
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Types.h>
#include <LibCore/Notifier.h>
#include <LibCore/TCPSocket.h>
#include "Command.h"
#include "Parser.h"
class Client : public RefCounted<Client> {
public:
static NonnullRefPtr<Client> create(int id, RefPtr<Core::TCPSocket> socket, int ptm_fd)
{
return adopt(*new Client(id, move(socket), ptm_fd));
}
Function<void()> on_exit;
protected:
Client(int id, RefPtr<Core::TCPSocket> socket, int ptm_fd);
void drain_socket();
void drain_pty();
void handle_data(const StringView&);
void handle_command(const Command& command);
void handle_error();
void send_data(StringView str);
void send_command(Command command);
void send_commands(Vector<Command> commands);
void quit();
private:
// client id
int m_id { 0 };
// client resources
RefPtr<Core::TCPSocket> m_socket;
Parser m_parser;
// pty resources
int m_ptm_fd { -1 };
RefPtr<Core::Notifier> m_ptm_notifier;
};

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <AK/Types.h>
#define CMD_WILL 0xfb
#define CMD_WONT 0xfc
#define CMD_DO 0xfd
#define CMD_DONT 0xfe
#define SUB_ECHO 0x01
#define SUB_SUPPRESS_GO_AHEAD 0x03
struct Command {
u8 command;
u8 subcommand;
String to_string() const
{
StringBuilder builder;
switch (command) {
case CMD_WILL:
builder.append("WILL");
break;
case CMD_WONT:
builder.append("WONT");
break;
case CMD_DO:
builder.append("DO");
break;
case CMD_DONT:
builder.append("DONT");
break;
default:
builder.append(String::format("UNKNOWN<%02x>", command));
break;
}
builder.append(" ");
switch (subcommand) {
case SUB_ECHO:
builder.append("ECHO");
break;
case SUB_SUPPRESS_GO_AHEAD:
builder.append("SUPPRESS_GO_AHEAD");
break;
default:
builder.append(String::format("UNKNOWN<%02x>"));
break;
}
return builder.to_string();
};
};

View file

@ -0,0 +1,10 @@
OBJS = \
Client.o \
Parser.o \
main.o
PROGRAM = TelnetServer
LIB_DEPS = Core
include ../../Makefile.common

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <AK/Function.h>
#include <AK/String.h>
#include <AK/Types.h>
#include "Parser.h"
void Parser::write(const StringView& data)
{
for (size_t i = 0; i < data.length(); i++) {
u8 ch = data[i];
switch (m_state) {
case State::Free:
switch (ch) {
case IAC:
m_state = State::ReadCommand;
break;
case '\r':
if (on_data)
on_data("\n");
break;
default:
if (on_data)
on_data(StringView(&ch, 1));
break;
}
break;
case State::ReadCommand:
switch (ch) {
case IAC: {
m_state = State::Free;
if (on_data)
on_data("\xff");
break;
}
case CMD_WILL:
case CMD_WONT:
case CMD_DO:
case CMD_DONT:
m_command = ch;
m_state = State::ReadSubcommand;
break;
default:
m_state = State::Error;
if (on_error)
on_error();
break;
}
break;
case State::ReadSubcommand: {
auto command = m_command;
m_command = 0;
m_state = State::Free;
if (on_command)
on_command({ command, ch });
break;
}
case State::Error:
// ignore everything
break;
}
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/Function.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Types.h>
#include "Command.h"
#define IAC 0xff
class Parser {
public:
Function<void(const Command&)> on_command;
Function<void(const StringView&)> on_data;
Function<void()> on_error;
void write(const StringView&);
protected:
enum State {
Free,
ReadCommand,
ReadSubcommand,
Error,
};
void write(const String& str);
private:
State m_state { State::Free };
u8 m_command { 0 };
};

View file

@ -0,0 +1,177 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "Client.h"
#include <AK/BufferStream.h>
#include <AK/ByteBuffer.h>
#include <AK/HashMap.h>
#include <AK/IPv4Address.h>
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <AK/Types.h>
#include <LibCore/EventLoop.h>
#include <LibCore/TCPServer.h>
#include <LibCore/TCPSocket.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
static void run_command(int ptm_fd, String command)
{
pid_t pid = fork();
if (pid == 0) {
const char* tty_name = ptsname(ptm_fd);
if (!tty_name) {
perror("ptsname");
exit(1);
}
close(ptm_fd);
int pts_fd = open(tty_name, O_RDWR);
if (pts_fd < 0) {
perror("open");
exit(1);
}
// NOTE: It's okay if this fails.
(void)ioctl(0, TIOCNOTTY);
close(0);
close(1);
close(2);
int rc = dup2(pts_fd, 0);
if (rc < 0) {
perror("dup2");
exit(1);
}
rc = dup2(pts_fd, 1);
if (rc < 0) {
perror("dup2");
exit(1);
}
rc = dup2(pts_fd, 2);
if (rc < 0) {
perror("dup2");
exit(1);
}
rc = close(pts_fd);
if (rc < 0) {
perror("close");
exit(1);
}
rc = ioctl(0, TIOCSCTTY);
if (rc < 0) {
perror("ioctl(TIOCSCTTY)");
exit(1);
}
const char* args[4] = { "/bin/Shell", nullptr, nullptr, nullptr };
if (!command.is_empty()) {
args[1] = "-c";
args[2] = command.characters();
}
const char* envs[] = { "TERM=xterm", "PATH=/bin:/usr/bin:/usr/local/bin", nullptr };
rc = execve("/bin/Shell", const_cast<char**>(args), const_cast<char**>(envs));
if (rc < 0) {
perror("execve");
exit(1);
}
ASSERT_NOT_REACHED();
}
}
int main(int argc, char** argv)
{
Core::EventLoop event_loop;
auto server = Core::TCPServer::construct();
int opt;
u16 port = 23;
const char* command = "";
while ((opt = getopt(argc, argv, "p:c:")) != -1) {
switch (opt) {
case 'p':
port = atoi(optarg);
break;
case 'c':
command = optarg;
break;
default:
fprintf(stderr, "Usage: %s [-p port] [-c command]", argv[0]);
exit(1);
}
}
if (!server->listen({}, port)) {
perror("listen");
exit(1);
}
HashMap<int, NonnullRefPtr<Client>> clients;
int next_id = 0;
server->on_ready_to_accept = [&next_id, &clients, &server, command] {
int id = next_id++;
auto client_socket = server->accept();
if (!client_socket) {
perror("accept");
return;
}
int ptm_fd = posix_openpt(O_RDWR);
if (ptm_fd < 0) {
perror("posix_openpt");
client_socket->close();
return;
}
if (grantpt(ptm_fd) < 0) {
perror("grantpt");
client_socket->close();
return;
}
if (unlockpt(ptm_fd) < 0) {
perror("unlockpt");
client_socket->close();
return;
}
run_command(ptm_fd, command);
auto client = Client::create(id, move(client_socket), ptm_fd);
client->on_exit = [&clients, id] { clients.remove(id); };
clients.set(id, client);
};
int rc = event_loop.exec();
if (rc != 0) {
fprintf(stderr, "event loop exited badly; rc=%d", rc);
exit(1);
}
return 0;
}

View file

@ -0,0 +1,230 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "Client.h"
#include <AK/FileSystemPath.h>
#include <AK/StringBuilder.h>
#include <LibCore/DateTime.h>
#include <LibCore/DirIterator.h>
#include <LibCore/File.h>
#include <LibHTTP/HttpRequest.h>
#include <stdio.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
namespace WebServer {
Client::Client(NonnullRefPtr<Core::TCPSocket> socket, Core::Object* parent)
: Core::Object(parent)
, m_socket(socket)
{
}
void Client::die()
{
remove_from_parent();
}
void Client::start()
{
m_socket->on_ready_to_read = [this] {
auto raw_request = m_socket->read_all();
if (raw_request.is_null()) {
die();
return;
}
dbg() << "Got raw request: '" << String::copy(raw_request) << "'";
handle_request(move(raw_request));
die();
};
}
void Client::handle_request(ByteBuffer raw_request)
{
auto request_or_error = HTTP::HttpRequest::from_raw_request(raw_request);
if (!request_or_error.has_value())
return;
auto& request = request_or_error.value();
dbg() << "Got HTTP request: " << request.method_name() << " " << request.resource();
for (auto& header : request.headers()) {
dbg() << " " << header.name << " => " << header.value;
}
if (request.method() != HTTP::HttpRequest::Method::GET) {
send_error_response(403, "Forbidden, bro!", request);
return;
}
auto requested_path = canonicalized_path(request.resource());
dbg() << "Canonical requested path: '" << requested_path << "'";
StringBuilder path_builder;
path_builder.append("/www/");
path_builder.append(requested_path);
auto real_path = path_builder.to_string();
if (Core::File::is_directory(real_path)) {
if (!request.resource().ends_with("/")) {
StringBuilder red;
red.append(requested_path);
red.append("/");
send_redirect(red.to_string(), request);
return;
}
StringBuilder index_html_path_builder;
index_html_path_builder.append(real_path);
index_html_path_builder.append("/index.html");
auto index_html_path = index_html_path_builder.to_string();
if (!Core::File::exists(index_html_path)) {
handle_directory_listing(requested_path, real_path, request);
return;
}
real_path = index_html_path;
}
auto file = Core::File::construct(real_path);
if (!file->open(Core::File::ReadOnly)) {
send_error_response(404, "Not found, bro!", request);
return;
}
send_response(file->read_all(), request);
}
void Client::send_response(StringView response, const HTTP::HttpRequest& request)
{
StringBuilder builder;
builder.append("HTTP/1.0 200 OK\r\n");
builder.append("Server: WebServer (SerenityOS)\r\n");
builder.append("Content-Type: text/html\r\n");
builder.append("\r\n");
m_socket->write(builder.to_string());
m_socket->write(response);
log_response(200, request);
}
void Client::send_redirect(StringView redirect_path, const HTTP::HttpRequest& request)
{
StringBuilder builder;
builder.append("HTTP/1.0 301 Moved Permanently\r\n");
builder.append("Location: ");
builder.append(redirect_path);
builder.append("\r\n");
builder.append("\r\n");
m_socket->write(builder.to_string());
log_response(301, request);
}
void Client::handle_directory_listing(const String& requested_path, const String& real_path, const HTTP::HttpRequest& request)
{
StringBuilder builder;
builder.append("<!DOCTYPE html>\n");
builder.append("<html>\n");
builder.append("<head><title>Index of ");
builder.append(escape_html_entities(requested_path));
builder.append("</title></head>\n");
builder.append("<body>\n");
builder.append("<h1>Index of ");
builder.append(escape_html_entities(requested_path));
builder.append("</h1>\n");
builder.append("<hr>\n");
builder.append("<pre>\n");
Core::DirIterator dt(real_path);
while (dt.has_next()) {
auto name = dt.next_path();
builder.append("<a href=\"");
// FIXME: urlencode
builder.append(name);
builder.append("\">");
builder.append(escape_html_entities(name));
builder.append("</a>");
for (size_t i = 0; i < (40 - name.length()); ++i)
builder.append(' ');
StringBuilder path_builder;
path_builder.append(real_path);
path_builder.append('/');
path_builder.append(name);
struct stat st;
memset(&st, 0, sizeof(st));
int rc = stat(path_builder.to_string().characters(), &st);
if (rc < 0) {
perror("stat");
}
builder.appendf(" %10d", st.st_size);
builder.appendf(" ");
builder.append(Core::DateTime::from_timestamp(st.st_mtime).to_string());
builder.append("\n");
}
builder.append("</pre>\n");
builder.append("<hr>\n");
builder.append("<i>Generated by WebServer (SerenityOS)</i>\n");
builder.append("</body>\n");
builder.append("</html>\n");
send_response(builder.to_string(), request);
}
void Client::send_error_response(unsigned code, const StringView& message, const HTTP::HttpRequest& request)
{
StringBuilder builder;
builder.appendf("HTTP/1.0 %u ", code);
builder.append(message);
builder.append("\r\n\r\n");
builder.append("<!DOCTYPE html><html><body><h1>");
builder.appendf("%u ", code);
builder.append(message);
builder.append("</h1></body></html>");
m_socket->write(builder.to_string());
log_response(code, request);
}
void Client::log_response(unsigned code, const HTTP::HttpRequest& request)
{
printf("%s :: %03u :: %s %s\n",
Core::DateTime::now().to_string().characters(),
code,
request.method_name().characters(),
request.resource().characters());
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <LibCore/Object.h>
#include <LibCore/TCPSocket.h>
#include <LibHTTP/Forward.h>
namespace WebServer {
class Client final : public Core::Object {
C_OBJECT(Client);
public:
void start();
private:
Client(NonnullRefPtr<Core::TCPSocket>, Core::Object* parent);
void handle_request(ByteBuffer);
void send_response(StringView, const HTTP::HttpRequest&);
void send_redirect(StringView redirect, const HTTP::HttpRequest& request);
void send_error_response(unsigned code, const StringView& message, const HTTP::HttpRequest&);
void die();
void log_response(unsigned code, const HTTP::HttpRequest&);
void handle_directory_listing(const String& requested_path, const String& real_path, const HTTP::HttpRequest&);
NonnullRefPtr<Core::TCPSocket> m_socket;
};
}

View file

@ -0,0 +1,9 @@
OBJS = \
Client.o \
main.o
PROGRAM = WebServer
LIB_DEPS = HTTP Core
include ../../Makefile.common

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "Client.h"
#include <LibCore/ArgsParser.h>
#include <LibCore/EventLoop.h>
#include <LibCore/TCPServer.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char** argv)
{
u16 default_port = 8000;
int port = default_port;
Core::ArgsParser args_parser;
args_parser.add_positional_argument(port, "Port to listen on", "port", Core::ArgsParser::Required::No);
args_parser.parse(argc, argv);
if ((u16)port != port) {
printf("Warning: invalid port number: %d\n", port);
port = default_port;
}
if (pledge("stdio accept rpath inet unix cpath fattr", nullptr) < 0) {
perror("pledge");
return 1;
}
Core::EventLoop loop;
auto server = Core::TCPServer::construct();
server->on_ready_to_accept = [&] {
auto client_socket = server->accept();
ASSERT(client_socket);
auto client = WebServer::Client::construct(client_socket.release_nonnull(), server);
client->start();
};
server->listen({}, port);
printf("Listening on 0.0.0.0:%d\n", port);
if (unveil("/www", "r") < 0) {
perror("unveil");
return 1;
}
unveil(nullptr, nullptr);
if (pledge("stdio accept rpath", nullptr) < 0) {
perror("pledge");
return 1;
}
return loop.exec();
}

View file

@ -0,0 +1,135 @@
/*
* Copyright (c) 2020-2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "AppletManager.h"
#include <AK/QuickSort.h>
#include <LibGfx/Painter.h>
#include <WindowServer/MenuManager.h>
namespace WindowServer {
static AppletManager* s_the;
Vector<String> order_vector;
AppletManager::AppletManager()
{
s_the = this;
auto wm_config = Core::ConfigFile::open("/etc/WindowServer/WindowServer.ini");
auto order = wm_config->read_entry("Applet", "Order");
order_vector = order.split(',');
}
AppletManager::~AppletManager()
{
}
AppletManager& AppletManager::the()
{
ASSERT(s_the);
return *s_the;
}
void AppletManager::event(Core::Event& event)
{
auto& mouse_event = static_cast<MouseEvent&>(event);
for (auto& applet : m_applets) {
if (!applet)
continue;
if (!applet->rect_in_menubar().contains(mouse_event.position()))
continue;
auto local_event = mouse_event.translated(-applet->rect_in_menubar().location());
applet->event(local_event);
}
}
void AppletManager::add_applet(Window& applet)
{
m_applets.append(applet.make_weak_ptr());
// Prune any dead weak pointers from the applet list.
m_applets.remove_all_matching([](auto& entry) {
return entry.is_null();
});
quick_sort(m_applets, [](auto& a, auto& b) {
auto index_a = order_vector.find_first_index(a->title());
auto index_b = order_vector.find_first_index(b->title());
return index_a.value_or(0) > index_b.value_or(0);
});
calculate_applet_rects(MenuManager::the().window());
}
void AppletManager::calculate_applet_rects(Window& window)
{
auto menubar_rect = window.rect();
int right_edge_x = menubar_rect.width() - 4;
for (auto& existing_applet : m_applets) {
Gfx::Rect new_applet_rect(right_edge_x - existing_applet->size().width(), 0, existing_applet->size().width(), existing_applet->size().height());
Gfx::Rect dummy_menubar_rect(0, 0, 0, 18);
new_applet_rect.center_vertically_within(dummy_menubar_rect);
existing_applet->set_rect_in_menubar(new_applet_rect);
right_edge_x = existing_applet->rect_in_menubar().x() - 4;
}
}
void AppletManager::remove_applet(Window& applet)
{
m_applets.remove_first_matching([&](auto& entry) {
return &applet == entry.ptr();
});
}
void AppletManager::draw()
{
for (auto& applet : m_applets) {
if (!applet)
continue;
draw_applet(*applet);
}
}
void AppletManager::draw_applet(const Window& applet)
{
if (!applet.backing_store())
return;
Gfx::Painter painter(*MenuManager::the().window().backing_store());
painter.fill_rect(applet.rect_in_menubar(), WindowManager::the().palette().window());
painter.blit(applet.rect_in_menubar().location(), *applet.backing_store(), applet.backing_store()->rect());
}
void AppletManager::invalidate_applet(const Window& applet, const Gfx::Rect& rect)
{
draw_applet(applet);
MenuManager::the().window().invalidate(rect.translated(applet.rect_in_menubar().location()));
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2020-2020, Hüseyin Aslıtürk <asliturk@hotmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <WindowServer/Window.h>
#include <WindowServer/WindowManager.h>
namespace WindowServer {
class AppletManager : public Core::Object {
C_OBJECT(AppletManager)
public:
AppletManager();
~AppletManager();
static AppletManager& the();
virtual void event(Core::Event&) override;
void add_applet(Window& applet);
void remove_applet(Window& applet);
void draw();
void invalidate_applet(const Window& applet, const Gfx::Rect& rect);
void calculate_applet_rects(Window& window);
private:
void draw_applet(const Window& applet);
Vector<WeakPtr<Window>> m_applets;
};
}

View file

@ -0,0 +1,115 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <LibGfx/CharacterBitmap.h>
#include <LibGfx/Painter.h>
#include <LibGfx/StylePainter.h>
#include <WindowServer/Button.h>
#include <WindowServer/Event.h>
#include <WindowServer/WindowManager.h>
namespace WindowServer {
Button::Button(WindowFrame& frame, NonnullRefPtr<Gfx::CharacterBitmap>&& bitmap, Function<void(Button&)>&& on_click_handler)
: on_click(move(on_click_handler))
, m_frame(frame)
, m_bitmap(move(bitmap))
{
}
Button::~Button()
{
}
void Button::paint(Gfx::Painter& painter)
{
auto palette = WindowManager::the().palette();
Gfx::PainterStateSaver saver(painter);
painter.translate(relative_rect().location());
Gfx::StylePainter::paint_button(painter, rect(), palette, Gfx::ButtonStyle::Normal, m_pressed, m_hovered);
auto x_location = rect().center();
x_location.move_by(-(m_bitmap->width() / 2), -(m_bitmap->height() / 2));
if (m_pressed)
x_location.move_by(1, 1);
painter.draw_bitmap(x_location, *m_bitmap, palette.button_text());
}
void Button::on_mouse_event(const MouseEvent& event)
{
auto& wm = WindowManager::the();
if (event.type() == Event::MouseDown && event.button() == MouseButton::Left) {
m_pressed = true;
wm.set_cursor_tracking_button(this);
wm.invalidate(screen_rect());
return;
}
if (event.type() == Event::MouseUp && event.button() == MouseButton::Left) {
if (wm.cursor_tracking_button() != this)
return;
wm.set_cursor_tracking_button(nullptr);
bool old_pressed = m_pressed;
m_pressed = false;
if (rect().contains(event.position())) {
if (on_click)
on_click(*this);
}
if (old_pressed != m_pressed) {
// Would like to compute:
// m_hovered = rect_after_action().contains(event.position());
// However, we don't know that rect yet. We can make an educated
// guess which also looks okay even when wrong:
m_hovered = false;
wm.invalidate(screen_rect());
}
return;
}
if (event.type() == Event::MouseMove) {
bool old_hovered = m_hovered;
m_hovered = rect().contains(event.position());
wm.set_hovered_button(m_hovered ? this : nullptr);
if (old_hovered != m_hovered)
wm.invalidate(screen_rect());
}
if (event.type() == Event::MouseMove && event.buttons() & (unsigned)MouseButton::Left) {
if (wm.cursor_tracking_button() != this)
return;
bool old_pressed = m_pressed;
m_pressed = m_hovered;
if (old_pressed != m_pressed)
wm.invalidate(screen_rect());
}
}
Gfx::Rect Button::screen_rect() const
{
return m_relative_rect.translated(m_frame.rect().location());
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/Function.h>
#include <AK/NonnullRefPtr.h>
#include <AK/Weakable.h>
#include <LibGfx/Rect.h>
#include <LibGfx/Forward.h>
namespace WindowServer {
class MouseEvent;
class WindowFrame;
class Button : public Weakable<Button> {
public:
Button(WindowFrame&, NonnullRefPtr<Gfx::CharacterBitmap>&&, Function<void(Button&)>&& on_click_handler);
~Button();
Gfx::Rect relative_rect() const { return m_relative_rect; }
void set_relative_rect(const Gfx::Rect& rect) { m_relative_rect = rect; }
Gfx::Rect rect() const { return { {}, m_relative_rect.size() }; }
Gfx::Rect screen_rect() const;
void paint(Gfx::Painter&);
void on_mouse_event(const MouseEvent&);
Function<void(Button&)> on_click;
bool is_visible() const { return m_visible; }
void set_bitmap(const Gfx::CharacterBitmap& bitmap) { m_bitmap = bitmap; }
private:
WindowFrame& m_frame;
Gfx::Rect m_relative_rect;
NonnullRefPtr<Gfx::CharacterBitmap> m_bitmap;
bool m_pressed { false };
bool m_visible { true };
bool m_hovered { false };
};
}

View file

@ -0,0 +1,833 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <AK/Badge.h>
#include <AK/SharedBuffer.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/SystemTheme.h>
#include <WindowServer/AppletManager.h>
#include <WindowServer/ClientConnection.h>
#include <WindowServer/Clipboard.h>
#include <WindowServer/Compositor.h>
#include <WindowServer/EventLoop.h>
#include <WindowServer/Menu.h>
#include <WindowServer/MenuBar.h>
#include <WindowServer/MenuItem.h>
#include <WindowServer/Screen.h>
#include <WindowServer/Window.h>
#include <WindowServer/WindowClientEndpoint.h>
#include <WindowServer/WindowManager.h>
#include <WindowServer/WindowSwitcher.h>
#include <errno.h>
#include <serenity.h>
#include <stdio.h>
#include <unistd.h>
namespace WindowServer {
HashMap<int, NonnullRefPtr<ClientConnection>>* s_connections;
static Gfx::Rect normalize_window_rect(Gfx::Rect rect, WindowType window_type)
{
auto min_size = 1;
if (window_type == WindowType::Normal)
min_size = 50;
Gfx::Rect normalized_rect = { rect.x(), rect.y(), max(rect.width(), min_size), max(rect.height(), min_size) };
return normalized_rect;
}
void ClientConnection::for_each_client(Function<void(ClientConnection&)> callback)
{
if (!s_connections)
return;
for (auto& it : *s_connections) {
callback(*it.value);
}
}
ClientConnection* ClientConnection::from_client_id(int client_id)
{
if (!s_connections)
return nullptr;
auto it = s_connections->find(client_id);
if (it == s_connections->end())
return nullptr;
return (*it).value.ptr();
}
ClientConnection::ClientConnection(Core::LocalSocket& client_socket, int client_id)
: IPC::ClientConnection<WindowServerEndpoint>(*this, client_socket, client_id)
{
if (!s_connections)
s_connections = new HashMap<int, NonnullRefPtr<ClientConnection>>;
s_connections->set(client_id, *this);
}
ClientConnection::~ClientConnection()
{
if (m_has_display_link)
Compositor::the().decrement_display_link_count({});
MenuManager::the().close_all_menus_from_client({}, *this);
auto windows = move(m_windows);
for (auto& window : windows) {
window.value->detach_client({});
if (window.value->type() == WindowType::MenuApplet)
AppletManager::the().remove_applet(window.value);
}
}
void ClientConnection::die()
{
deferred_invoke([this](auto&) {
s_connections->remove(client_id());
});
}
void ClientConnection::notify_about_new_screen_rect(const Gfx::Rect& rect)
{
post_message(Messages::WindowClient::ScreenRectChanged(rect));
}
void ClientConnection::notify_about_clipboard_contents_changed()
{
post_message(Messages::WindowClient::ClipboardContentsChanged(Clipboard::the().data_type()));
}
OwnPtr<Messages::WindowServer::CreateMenubarResponse> ClientConnection::handle(const Messages::WindowServer::CreateMenubar&)
{
int menubar_id = m_next_menubar_id++;
auto menubar = make<MenuBar>(*this, menubar_id);
m_menubars.set(menubar_id, move(menubar));
return make<Messages::WindowServer::CreateMenubarResponse>(menubar_id);
}
OwnPtr<Messages::WindowServer::DestroyMenubarResponse> ClientConnection::handle(const Messages::WindowServer::DestroyMenubar& message)
{
int menubar_id = message.menubar_id();
auto it = m_menubars.find(menubar_id);
if (it == m_menubars.end()) {
did_misbehave("DestroyMenubar: Bad menubar ID");
return nullptr;
}
auto& menubar = *(*it).value;
MenuManager::the().close_menubar(menubar);
m_menubars.remove(it);
return make<Messages::WindowServer::DestroyMenubarResponse>();
}
OwnPtr<Messages::WindowServer::CreateMenuResponse> ClientConnection::handle(const Messages::WindowServer::CreateMenu& message)
{
int menu_id = m_next_menu_id++;
auto menu = Menu::construct(this, menu_id, message.menu_title());
m_menus.set(menu_id, move(menu));
return make<Messages::WindowServer::CreateMenuResponse>(menu_id);
}
OwnPtr<Messages::WindowServer::DestroyMenuResponse> ClientConnection::handle(const Messages::WindowServer::DestroyMenu& message)
{
int menu_id = message.menu_id();
auto it = m_menus.find(menu_id);
if (it == m_menus.end()) {
did_misbehave("DestroyMenu: Bad menu ID");
return nullptr;
}
auto& menu = *(*it).value;
menu.close();
m_menus.remove(it);
remove_child(menu);
return make<Messages::WindowServer::DestroyMenuResponse>();
}
OwnPtr<Messages::WindowServer::SetApplicationMenubarResponse> ClientConnection::handle(const Messages::WindowServer::SetApplicationMenubar& message)
{
int menubar_id = message.menubar_id();
auto it = m_menubars.find(menubar_id);
if (it == m_menubars.end()) {
did_misbehave("SetApplicationMenubar: Bad menubar ID");
return nullptr;
}
auto& menubar = *(*it).value;
m_app_menubar = menubar.make_weak_ptr();
WindowManager::the().notify_client_changed_app_menubar(*this);
return make<Messages::WindowServer::SetApplicationMenubarResponse>();
}
OwnPtr<Messages::WindowServer::AddMenuToMenubarResponse> ClientConnection::handle(const Messages::WindowServer::AddMenuToMenubar& message)
{
int menubar_id = message.menubar_id();
int menu_id = message.menu_id();
auto it = m_menubars.find(menubar_id);
auto jt = m_menus.find(menu_id);
if (it == m_menubars.end()) {
did_misbehave("AddMenuToMenubar: Bad menubar ID");
return nullptr;
}
if (jt == m_menus.end()) {
did_misbehave("AddMenuToMenubar: Bad menu ID");
return nullptr;
}
auto& menubar = *(*it).value;
auto& menu = *(*jt).value;
menubar.add_menu(menu);
return make<Messages::WindowServer::AddMenuToMenubarResponse>();
}
OwnPtr<Messages::WindowServer::AddMenuItemResponse> ClientConnection::handle(const Messages::WindowServer::AddMenuItem& message)
{
int menu_id = message.menu_id();
unsigned identifier = message.identifier();
auto it = m_menus.find(menu_id);
if (it == m_menus.end()) {
dbg() << "AddMenuItem: Bad menu ID: " << menu_id;
return nullptr;
}
auto& menu = *(*it).value;
auto menu_item = make<MenuItem>(menu, identifier, message.text(), message.shortcut(), message.enabled(), message.checkable(), message.checked());
if (message.icon_buffer_id() != -1) {
auto icon_buffer = SharedBuffer::create_from_shbuf_id(message.icon_buffer_id());
if (!icon_buffer)
return nullptr;
// FIXME: Verify that the icon buffer can accomodate a 16x16 bitmap view.
auto shared_icon = Gfx::Bitmap::create_with_shared_buffer(Gfx::BitmapFormat::RGBA32, icon_buffer.release_nonnull(), { 16, 16 });
menu_item->set_icon(shared_icon);
}
menu_item->set_submenu_id(message.submenu_id());
menu_item->set_exclusive(message.exclusive());
menu.add_item(move(menu_item));
return make<Messages::WindowServer::AddMenuItemResponse>();
}
OwnPtr<Messages::WindowServer::PopupMenuResponse> ClientConnection::handle(const Messages::WindowServer::PopupMenu& message)
{
int menu_id = message.menu_id();
auto position = message.screen_position();
auto it = m_menus.find(menu_id);
if (it == m_menus.end()) {
did_misbehave("PopupMenu: Bad menu ID");
return nullptr;
}
auto& menu = *(*it).value;
menu.popup(position);
return make<Messages::WindowServer::PopupMenuResponse>();
}
OwnPtr<Messages::WindowServer::DismissMenuResponse> ClientConnection::handle(const Messages::WindowServer::DismissMenu& message)
{
int menu_id = message.menu_id();
auto it = m_menus.find(menu_id);
if (it == m_menus.end()) {
did_misbehave("DismissMenu: Bad menu ID");
return nullptr;
}
auto& menu = *(*it).value;
menu.close();
return make<Messages::WindowServer::DismissMenuResponse>();
}
OwnPtr<Messages::WindowServer::UpdateMenuItemResponse> ClientConnection::handle(const Messages::WindowServer::UpdateMenuItem& message)
{
int menu_id = message.menu_id();
auto it = m_menus.find(menu_id);
if (it == m_menus.end()) {
did_misbehave("UpdateMenuItem: Bad menu ID");
return nullptr;
}
auto& menu = *(*it).value;
auto* menu_item = menu.item_with_identifier(message.identifier());
if (!menu_item) {
did_misbehave("UpdateMenuItem: Bad menu item identifier");
return nullptr;
}
menu_item->set_text(message.text());
menu_item->set_shortcut_text(message.shortcut());
menu_item->set_enabled(message.enabled());
menu_item->set_checkable(message.checkable());
if (message.checkable())
menu_item->set_checked(message.checked());
return make<Messages::WindowServer::UpdateMenuItemResponse>();
}
OwnPtr<Messages::WindowServer::AddMenuSeparatorResponse> ClientConnection::handle(const Messages::WindowServer::AddMenuSeparator& message)
{
int menu_id = message.menu_id();
auto it = m_menus.find(menu_id);
if (it == m_menus.end()) {
did_misbehave("AddMenuSeparator: Bad menu ID");
return nullptr;
}
auto& menu = *(*it).value;
menu.add_item(make<MenuItem>(menu, MenuItem::Separator));
return make<Messages::WindowServer::AddMenuSeparatorResponse>();
}
OwnPtr<Messages::WindowServer::MoveWindowToFrontResponse> ClientConnection::handle(const Messages::WindowServer::MoveWindowToFront& message)
{
auto it = m_windows.find(message.window_id());
if (it == m_windows.end()) {
did_misbehave("MoveWindowToFront: Bad window ID");
return nullptr;
}
WindowManager::the().move_to_front_and_make_active(*(*it).value);
return make<Messages::WindowServer::MoveWindowToFrontResponse>();
}
OwnPtr<Messages::WindowServer::SetFullscreenResponse> ClientConnection::handle(const Messages::WindowServer::SetFullscreen& message)
{
auto it = m_windows.find(message.window_id());
if (it == m_windows.end()) {
did_misbehave("SetFullscreen: Bad window ID");
return nullptr;
}
it->value->set_fullscreen(message.fullscreen());
return make<Messages::WindowServer::SetFullscreenResponse>();
}
OwnPtr<Messages::WindowServer::SetWindowOpacityResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowOpacity& message)
{
auto it = m_windows.find(message.window_id());
if (it == m_windows.end()) {
did_misbehave("SetWindowOpacity: Bad window ID");
return nullptr;
}
it->value->set_opacity(message.opacity());
return make<Messages::WindowServer::SetWindowOpacityResponse>();
}
void ClientConnection::handle(const Messages::WindowServer::AsyncSetWallpaper& message)
{
Compositor::the().set_wallpaper(message.path(), [&](bool success) {
post_message(Messages::WindowClient::AsyncSetWallpaperFinished(success));
});
}
OwnPtr<Messages::WindowServer::SetBackgroundColorResponse> ClientConnection::handle(const Messages::WindowServer::SetBackgroundColor& message)
{
Compositor::the().set_background_color(message.background_color());
return make<Messages::WindowServer::SetBackgroundColorResponse>();
}
OwnPtr<Messages::WindowServer::SetWallpaperModeResponse> ClientConnection::handle(const Messages::WindowServer::SetWallpaperMode& message)
{
Compositor::the().set_wallpaper_mode(message.mode());
return make<Messages::WindowServer::SetWallpaperModeResponse>();
}
OwnPtr<Messages::WindowServer::GetWallpaperResponse> ClientConnection::handle(const Messages::WindowServer::GetWallpaper&)
{
return make<Messages::WindowServer::GetWallpaperResponse>(Compositor::the().wallpaper_path());
}
OwnPtr<Messages::WindowServer::SetResolutionResponse> ClientConnection::handle(const Messages::WindowServer::SetResolution& message)
{
return make<Messages::WindowServer::SetResolutionResponse>(WindowManager::the().set_resolution(message.resolution().width(), message.resolution().height()), WindowManager::the().resolution());
}
OwnPtr<Messages::WindowServer::SetWindowTitleResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowTitle& message)
{
auto it = m_windows.find(message.window_id());
if (it == m_windows.end()) {
did_misbehave("SetWindowTitle: Bad window ID");
return nullptr;
}
it->value->set_title(message.title());
return make<Messages::WindowServer::SetWindowTitleResponse>();
}
OwnPtr<Messages::WindowServer::GetWindowTitleResponse> ClientConnection::handle(const Messages::WindowServer::GetWindowTitle& message)
{
auto it = m_windows.find(message.window_id());
if (it == m_windows.end()) {
did_misbehave("GetWindowTitle: Bad window ID");
return nullptr;
}
return make<Messages::WindowServer::GetWindowTitleResponse>(it->value->title());
}
OwnPtr<Messages::WindowServer::SetWindowIconBitmapResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowIconBitmap& message)
{
auto it = m_windows.find(message.window_id());
if (it == m_windows.end()) {
did_misbehave("SetWindowIconBitmap: Bad window ID");
return nullptr;
}
auto& window = *(*it).value;
if (message.icon().is_valid()) {
window.set_icon(*message.icon().bitmap());
} else {
window.set_default_icon();
}
window.frame().invalidate_title_bar();
WindowManager::the().tell_wm_listeners_window_icon_changed(window);
return make<Messages::WindowServer::SetWindowIconBitmapResponse>();
}
OwnPtr<Messages::WindowServer::SetWindowRectResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowRect& message)
{
int window_id = message.window_id();
auto it = m_windows.find(window_id);
if (it == m_windows.end()) {
did_misbehave("SetWindowRect: Bad window ID");
return nullptr;
}
auto& window = *(*it).value;
if (window.is_fullscreen()) {
dbg() << "ClientConnection: Ignoring SetWindowRect request for fullscreen window";
return nullptr;
}
auto normalized_rect = normalize_window_rect(message.rect(), window.type());
window.set_rect(normalized_rect);
window.request_update(normalized_rect);
return make<Messages::WindowServer::SetWindowRectResponse>(normalized_rect);
}
OwnPtr<Messages::WindowServer::GetWindowRectResponse> ClientConnection::handle(const Messages::WindowServer::GetWindowRect& message)
{
int window_id = message.window_id();
auto it = m_windows.find(window_id);
if (it == m_windows.end()) {
did_misbehave("GetWindowRect: Bad window ID");
return nullptr;
}
return make<Messages::WindowServer::GetWindowRectResponse>(it->value->rect());
}
OwnPtr<Messages::WindowServer::SetClipboardContentsResponse> ClientConnection::handle(const Messages::WindowServer::SetClipboardContents& message)
{
auto shared_buffer = SharedBuffer::create_from_shbuf_id(message.shbuf_id());
if (!shared_buffer) {
did_misbehave("SetClipboardContents: Bad shared buffer ID");
return nullptr;
}
Clipboard::the().set_data(*shared_buffer, message.content_size(), message.content_type());
return make<Messages::WindowServer::SetClipboardContentsResponse>();
}
OwnPtr<Messages::WindowServer::GetClipboardContentsResponse> ClientConnection::handle(const Messages::WindowServer::GetClipboardContents&)
{
auto& clipboard = Clipboard::the();
i32 shbuf_id = -1;
if (clipboard.size()) {
// FIXME: Optimize case where an app is copy/pasting within itself.
// We can just reuse the SharedBuffer then, since it will have the same peer PID.
// It would be even nicer if a SharedBuffer could have an arbitrary number of clients..
RefPtr<SharedBuffer> shared_buffer = SharedBuffer::create_with_size(clipboard.size());
ASSERT(shared_buffer);
memcpy(shared_buffer->data(), clipboard.data(), clipboard.size());
shared_buffer->seal();
shared_buffer->share_with(client_pid());
shbuf_id = shared_buffer->shbuf_id();
// FIXME: This is a workaround for the fact that SharedBuffers will go away if neither side is retaining them.
// After we respond to GetClipboardContents, we have to wait for the client to ref the buffer on his side.
m_last_sent_clipboard_content = move(shared_buffer);
}
return make<Messages::WindowServer::GetClipboardContentsResponse>(shbuf_id, clipboard.size(), clipboard.data_type());
}
Window* ClientConnection::window_from_id(i32 window_id)
{
auto it = m_windows.find(window_id);
if (it == m_windows.end())
return nullptr;
return it->value.ptr();
}
OwnPtr<Messages::WindowServer::CreateWindowResponse> ClientConnection::handle(const Messages::WindowServer::CreateWindow& message)
{
int window_id = m_next_window_id++;
auto window = Window::construct(*this, (WindowType)message.type(), window_id, message.modal(), message.minimizable(), message.frameless(), message.resizable(), message.fullscreen());
if (message.parent_window_id()) {
auto* parent_window = window_from_id(message.parent_window_id());
if (!parent_window) {
did_misbehave("CreateWindow with bad parent_window_id");
return nullptr;
}
if (parent_window->window_id() == window_id) {
did_misbehave("CreateWindow trying to make a window with itself as parent");
return nullptr;
}
window->set_parent_window(*parent_window);
}
window->set_has_alpha_channel(message.has_alpha_channel());
window->set_title(message.title());
if (!message.fullscreen()) {
auto normalized_rect = normalize_window_rect(message.rect(), window->type());
window->set_rect(normalized_rect);
}
if (window->type() == WindowType::Desktop) {
window->set_rect(WindowManager::the().desktop_rect());
window->recalculate_rect();
}
window->set_opacity(message.opacity());
window->set_size_increment(message.size_increment());
window->set_base_size(message.base_size());
window->invalidate();
if (window->type() == WindowType::MenuApplet)
AppletManager::the().add_applet(*window);
m_windows.set(window_id, move(window));
return make<Messages::WindowServer::CreateWindowResponse>(window_id);
}
void ClientConnection::destroy_window(Window& window, Vector<i32>& destroyed_window_ids)
{
for (auto& child_window : window.child_windows()) {
if (!child_window)
continue;
ASSERT(child_window->window_id() != window.window_id());
destroy_window(*child_window, destroyed_window_ids);
}
destroyed_window_ids.append(window.window_id());
if (window.type() == WindowType::MenuApplet)
AppletManager::the().remove_applet(window);
WindowManager::the().invalidate(window);
remove_child(window);
m_windows.remove(window.window_id());
}
OwnPtr<Messages::WindowServer::DestroyWindowResponse> ClientConnection::handle(const Messages::WindowServer::DestroyWindow& message)
{
auto it = m_windows.find(message.window_id());
if (it == m_windows.end()) {
did_misbehave("DestroyWindow: Bad window ID");
return nullptr;
}
auto& window = *(*it).value;
Vector<i32> destroyed_window_ids;
destroy_window(window, destroyed_window_ids);
return make<Messages::WindowServer::DestroyWindowResponse>(destroyed_window_ids);
}
void ClientConnection::post_paint_message(Window& window, bool ignore_occlusion)
{
auto rect_set = window.take_pending_paint_rects();
if (window.is_minimized() || (!ignore_occlusion && window.is_occluded()))
return;
post_message(Messages::WindowClient::Paint(window.window_id(), window.size(), rect_set.rects()));
}
void ClientConnection::handle(const Messages::WindowServer::InvalidateRect& message)
{
auto it = m_windows.find(message.window_id());
if (it == m_windows.end()) {
did_misbehave("InvalidateRect: Bad window ID");
return;
}
auto& window = *(*it).value;
for (size_t i = 0; i < message.rects().size(); ++i)
window.request_update(message.rects()[i].intersected({ {}, window.size() }), message.ignore_occlusion());
}
void ClientConnection::handle(const Messages::WindowServer::DidFinishPainting& message)
{
int window_id = message.window_id();
auto it = m_windows.find(window_id);
if (it == m_windows.end()) {
did_misbehave("DidFinishPainting: Bad window ID");
return;
}
auto& window = *(*it).value;
for (auto& rect : message.rects())
WindowManager::the().invalidate(window, rect);
WindowSwitcher::the().refresh_if_needed();
}
OwnPtr<Messages::WindowServer::SetWindowBackingStoreResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowBackingStore& message)
{
int window_id = message.window_id();
auto it = m_windows.find(window_id);
if (it == m_windows.end()) {
did_misbehave("SetWindowBackingStore: Bad window ID");
return nullptr;
}
auto& window = *(*it).value;
if (window.last_backing_store() && window.last_backing_store()->shbuf_id() == message.shbuf_id()) {
window.swap_backing_stores();
} else {
auto shared_buffer = SharedBuffer::create_from_shbuf_id(message.shbuf_id());
if (!shared_buffer)
return make<Messages::WindowServer::SetWindowBackingStoreResponse>();
auto backing_store = Gfx::Bitmap::create_with_shared_buffer(
message.has_alpha_channel() ? Gfx::BitmapFormat::RGBA32 : Gfx::BitmapFormat::RGB32,
*shared_buffer,
message.size());
window.set_backing_store(move(backing_store));
}
if (message.flush_immediately())
window.invalidate();
return make<Messages::WindowServer::SetWindowBackingStoreResponse>();
}
OwnPtr<Messages::WindowServer::SetGlobalCursorTrackingResponse> ClientConnection::handle(const Messages::WindowServer::SetGlobalCursorTracking& message)
{
int window_id = message.window_id();
auto it = m_windows.find(window_id);
if (it == m_windows.end()) {
did_misbehave("SetGlobalCursorTracking: Bad window ID");
return nullptr;
}
it->value->set_global_cursor_tracking_enabled(message.enabled());
return make<Messages::WindowServer::SetGlobalCursorTrackingResponse>();
}
OwnPtr<Messages::WindowServer::SetWindowOverrideCursorResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowOverrideCursor& message)
{
auto it = m_windows.find(message.window_id());
if (it == m_windows.end()) {
did_misbehave("SetWindowOverrideCursor: Bad window ID");
return nullptr;
}
auto& window = *(*it).value;
window.set_override_cursor(Cursor::create((StandardCursor)message.cursor_type()));
return make<Messages::WindowServer::SetWindowOverrideCursorResponse>();
}
OwnPtr<Messages::WindowServer::SetWindowHasAlphaChannelResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowHasAlphaChannel& message)
{
auto it = m_windows.find(message.window_id());
if (it == m_windows.end()) {
did_misbehave("SetWindowHasAlphaChannel: Bad window ID");
return nullptr;
}
it->value->set_has_alpha_channel(message.has_alpha_channel());
return make<Messages::WindowServer::SetWindowHasAlphaChannelResponse>();
}
void ClientConnection::handle(const Messages::WindowServer::WM_SetActiveWindow& message)
{
auto* client = ClientConnection::from_client_id(message.client_id());
if (!client) {
did_misbehave("WM_SetActiveWindow: Bad client ID");
return;
}
auto it = client->m_windows.find(message.window_id());
if (it == client->m_windows.end()) {
did_misbehave("WM_SetActiveWindow: Bad window ID");
return;
}
auto& window = *(*it).value;
window.set_minimized(false);
WindowManager::the().move_to_front_and_make_active(window);
}
void ClientConnection::handle(const Messages::WindowServer::WM_PopupWindowMenu& message)
{
auto* client = ClientConnection::from_client_id(message.client_id());
if (!client) {
did_misbehave("WM_PopupWindowMenu: Bad client ID");
return;
}
auto it = client->m_windows.find(message.window_id());
if (it == client->m_windows.end()) {
did_misbehave("WM_PopupWindowMenu: Bad window ID");
return;
}
auto& window = *(*it).value;
window.popup_window_menu(message.screen_position());
}
void ClientConnection::handle(const Messages::WindowServer::WM_StartWindowResize& request)
{
auto* client = ClientConnection::from_client_id(request.client_id());
if (!client) {
did_misbehave("WM_StartWindowResize: Bad client ID");
return;
}
auto it = client->m_windows.find(request.window_id());
if (it == client->m_windows.end()) {
did_misbehave("WM_StartWindowResize: Bad window ID");
return;
}
auto& window = *(*it).value;
// FIXME: We are cheating a bit here by using the current cursor location and hard-coding the left button.
// Maybe the client should be allowed to specify what initiated this request?
WindowManager::the().start_window_resize(window, Screen::the().cursor_location(), MouseButton::Left);
}
void ClientConnection::handle(const Messages::WindowServer::WM_SetWindowMinimized& message)
{
auto* client = ClientConnection::from_client_id(message.client_id());
if (!client) {
did_misbehave("WM_SetWindowMinimized: Bad client ID");
return;
}
auto it = client->m_windows.find(message.window_id());
if (it == client->m_windows.end()) {
did_misbehave("WM_SetWindowMinimized: Bad window ID");
return;
}
auto& window = *(*it).value;
window.set_minimized(message.minimized());
}
OwnPtr<Messages::WindowServer::GreetResponse> ClientConnection::handle(const Messages::WindowServer::Greet&)
{
return make<Messages::WindowServer::GreetResponse>(client_id(), Screen::the().rect(), Gfx::current_system_theme_buffer_id());
}
bool ClientConnection::is_showing_modal_window() const
{
for (auto& it : m_windows) {
auto& window = *it.value;
if (window.is_visible() && window.is_modal())
return true;
}
return false;
}
void ClientConnection::handle(const Messages::WindowServer::WM_SetWindowTaskbarRect& message)
{
auto* client = ClientConnection::from_client_id(message.client_id());
if (!client) {
did_misbehave("WM_SetWindowTaskbarRect: Bad client ID");
return;
}
auto it = client->m_windows.find(message.window_id());
if (it == client->m_windows.end()) {
did_misbehave("WM_SetWindowTaskbarRect: Bad window ID");
return;
}
auto& window = *(*it).value;
window.set_taskbar_rect(message.rect());
}
OwnPtr<Messages::WindowServer::StartDragResponse> ClientConnection::handle(const Messages::WindowServer::StartDrag& message)
{
auto& wm = WindowManager::the();
if (wm.dnd_client())
return make<Messages::WindowServer::StartDragResponse>(false);
RefPtr<Gfx::Bitmap> bitmap;
if (message.bitmap_id() != -1) {
auto shared_buffer = SharedBuffer::create_from_shbuf_id(message.bitmap_id());
ssize_t size_in_bytes = message.bitmap_size().area() * sizeof(Gfx::RGBA32);
if (size_in_bytes > shared_buffer->size()) {
did_misbehave("SetAppletBackingStore: Shared buffer is too small for applet size");
return nullptr;
}
bitmap = Gfx::Bitmap::create_with_shared_buffer(Gfx::BitmapFormat::RGBA32, *shared_buffer, message.bitmap_size());
}
wm.start_dnd_drag(*this, message.text(), bitmap, message.data_type(), message.data());
return make<Messages::WindowServer::StartDragResponse>(true);
}
OwnPtr<Messages::WindowServer::SetSystemMenuResponse> ClientConnection::handle(const Messages::WindowServer::SetSystemMenu& message)
{
auto it = m_menus.find(message.menu_id());
if (it == m_menus.end()) {
did_misbehave("SetSystemMenu called with invalid menu ID");
return nullptr;
}
auto& menu = it->value;
MenuManager::the().set_system_menu(menu);
return make<Messages::WindowServer::SetSystemMenuResponse>();
}
OwnPtr<Messages::WindowServer::SetSystemThemeResponse> ClientConnection::handle(const Messages::WindowServer::SetSystemTheme& message)
{
bool success = WindowManager::the().update_theme(message.theme_path(), message.theme_name());
return make<Messages::WindowServer::SetSystemThemeResponse>(success);
}
OwnPtr<Messages::WindowServer::GetSystemThemeResponse> ClientConnection::handle(const Messages::WindowServer::GetSystemTheme&)
{
auto wm_config = Core::ConfigFile::open("/etc/WindowServer/WindowServer.ini");
auto name = wm_config->read_entry("Theme", "Name");
return make<Messages::WindowServer::GetSystemThemeResponse>(name);
}
void ClientConnection::boost()
{
// FIXME: Re-enable this when we have a solution for boosting.
#if 0
if (set_process_boost(client_pid(), 10) < 0)
perror("boost: set_process_boost");
#endif
}
void ClientConnection::deboost()
{
// FIXME: Re-enable this when we have a solution for boosting.
#if 0
if (set_process_boost(client_pid(), 0) < 0)
perror("deboost: set_process_boost");
#endif
}
OwnPtr<Messages::WindowServer::SetWindowBaseSizeAndSizeIncrementResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowBaseSizeAndSizeIncrement& message)
{
auto it = m_windows.find(message.window_id());
if (it == m_windows.end()) {
did_misbehave("SetWindowBaseSizeAndSizeIncrementResponse: Bad window ID");
return nullptr;
}
auto& window = *it->value;
window.set_base_size(message.base_size());
window.set_size_increment(message.size_increment());
return make<Messages::WindowServer::SetWindowBaseSizeAndSizeIncrementResponse>();
}
void ClientConnection::handle(const Messages::WindowServer::EnableDisplayLink&)
{
if (m_has_display_link)
return;
m_has_display_link = true;
Compositor::the().increment_display_link_count({});
}
void ClientConnection::handle(const Messages::WindowServer::DisableDisplayLink&)
{
if (!m_has_display_link)
return;
m_has_display_link = false;
Compositor::the().decrement_display_link_count({});
}
void ClientConnection::notify_display_link(Badge<Compositor>)
{
if (!m_has_display_link)
return;
post_message(Messages::WindowClient::DisplayLinkNotification());
}
}

View file

@ -0,0 +1,148 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/Badge.h>
#include <AK/Function.h>
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
#include <AK/WeakPtr.h>
#include <LibCore/Object.h>
#include <LibGfx/Bitmap.h>
#include <LibIPC/ClientConnection.h>
#include <WindowServer/Event.h>
#include <WindowServer/WindowServerEndpoint.h>
namespace WindowServer {
class Compositor;
class Window;
class Menu;
class MenuBar;
class ClientConnection final
: public IPC::ClientConnection<WindowServerEndpoint>
, public WindowServerEndpoint {
C_OBJECT(ClientConnection)
public:
~ClientConnection() override;
virtual void die() override;
void boost();
void deboost();
static ClientConnection* from_client_id(int client_id);
static void for_each_client(Function<void(ClientConnection&)>);
MenuBar* app_menubar() { return m_app_menubar.ptr(); }
bool is_showing_modal_window() const;
void notify_about_new_screen_rect(const Gfx::Rect&);
void notify_about_clipboard_contents_changed();
void post_paint_message(Window&, bool ignore_occlusion = false);
Menu* find_menu_by_id(int menu_id)
{
auto menu = m_menus.get(menu_id);
if (!menu.has_value())
return nullptr;
return const_cast<Menu*>(menu.value().ptr());
}
void notify_display_link(Badge<Compositor>);
private:
explicit ClientConnection(Core::LocalSocket&, int client_id);
void destroy_window(Window&, Vector<i32>& destroyed_window_ids);
virtual OwnPtr<Messages::WindowServer::GreetResponse> handle(const Messages::WindowServer::Greet&) override;
virtual OwnPtr<Messages::WindowServer::CreateMenubarResponse> handle(const Messages::WindowServer::CreateMenubar&) override;
virtual OwnPtr<Messages::WindowServer::DestroyMenubarResponse> handle(const Messages::WindowServer::DestroyMenubar&) override;
virtual OwnPtr<Messages::WindowServer::CreateMenuResponse> handle(const Messages::WindowServer::CreateMenu&) override;
virtual OwnPtr<Messages::WindowServer::DestroyMenuResponse> handle(const Messages::WindowServer::DestroyMenu&) override;
virtual OwnPtr<Messages::WindowServer::AddMenuToMenubarResponse> handle(const Messages::WindowServer::AddMenuToMenubar&) override;
virtual OwnPtr<Messages::WindowServer::SetApplicationMenubarResponse> handle(const Messages::WindowServer::SetApplicationMenubar&) override;
virtual OwnPtr<Messages::WindowServer::AddMenuItemResponse> handle(const Messages::WindowServer::AddMenuItem&) override;
virtual OwnPtr<Messages::WindowServer::AddMenuSeparatorResponse> handle(const Messages::WindowServer::AddMenuSeparator&) override;
virtual OwnPtr<Messages::WindowServer::UpdateMenuItemResponse> handle(const Messages::WindowServer::UpdateMenuItem&) override;
virtual OwnPtr<Messages::WindowServer::CreateWindowResponse> handle(const Messages::WindowServer::CreateWindow&) override;
virtual OwnPtr<Messages::WindowServer::DestroyWindowResponse> handle(const Messages::WindowServer::DestroyWindow&) override;
virtual OwnPtr<Messages::WindowServer::SetWindowTitleResponse> handle(const Messages::WindowServer::SetWindowTitle&) override;
virtual OwnPtr<Messages::WindowServer::GetWindowTitleResponse> handle(const Messages::WindowServer::GetWindowTitle&) override;
virtual OwnPtr<Messages::WindowServer::SetWindowRectResponse> handle(const Messages::WindowServer::SetWindowRect&) override;
virtual OwnPtr<Messages::WindowServer::GetWindowRectResponse> handle(const Messages::WindowServer::GetWindowRect&) override;
virtual void handle(const Messages::WindowServer::InvalidateRect&) override;
virtual void handle(const Messages::WindowServer::DidFinishPainting&) override;
virtual OwnPtr<Messages::WindowServer::SetGlobalCursorTrackingResponse> handle(const Messages::WindowServer::SetGlobalCursorTracking&) override;
virtual OwnPtr<Messages::WindowServer::SetWindowOpacityResponse> handle(const Messages::WindowServer::SetWindowOpacity&) override;
virtual OwnPtr<Messages::WindowServer::SetWindowBackingStoreResponse> handle(const Messages::WindowServer::SetWindowBackingStore&) override;
virtual OwnPtr<Messages::WindowServer::GetClipboardContentsResponse> handle(const Messages::WindowServer::GetClipboardContents&) override;
virtual OwnPtr<Messages::WindowServer::SetClipboardContentsResponse> handle(const Messages::WindowServer::SetClipboardContents&) override;
virtual void handle(const Messages::WindowServer::WM_SetActiveWindow&) override;
virtual void handle(const Messages::WindowServer::WM_SetWindowMinimized&) override;
virtual void handle(const Messages::WindowServer::WM_StartWindowResize&) override;
virtual void handle(const Messages::WindowServer::WM_PopupWindowMenu&) override;
virtual OwnPtr<Messages::WindowServer::SetWindowHasAlphaChannelResponse> handle(const Messages::WindowServer::SetWindowHasAlphaChannel&) override;
virtual OwnPtr<Messages::WindowServer::MoveWindowToFrontResponse> handle(const Messages::WindowServer::MoveWindowToFront&) override;
virtual OwnPtr<Messages::WindowServer::SetFullscreenResponse> handle(const Messages::WindowServer::SetFullscreen&) override;
virtual void handle(const Messages::WindowServer::AsyncSetWallpaper&) override;
virtual OwnPtr<Messages::WindowServer::SetBackgroundColorResponse> handle(const Messages::WindowServer::SetBackgroundColor&) override;
virtual OwnPtr<Messages::WindowServer::SetWallpaperModeResponse> handle(const Messages::WindowServer::SetWallpaperMode&) override;
virtual OwnPtr<Messages::WindowServer::GetWallpaperResponse> handle(const Messages::WindowServer::GetWallpaper&) override;
virtual OwnPtr<Messages::WindowServer::SetResolutionResponse> handle(const Messages::WindowServer::SetResolution&) override;
virtual OwnPtr<Messages::WindowServer::SetWindowOverrideCursorResponse> handle(const Messages::WindowServer::SetWindowOverrideCursor&) override;
virtual OwnPtr<Messages::WindowServer::PopupMenuResponse> handle(const Messages::WindowServer::PopupMenu&) override;
virtual OwnPtr<Messages::WindowServer::DismissMenuResponse> handle(const Messages::WindowServer::DismissMenu&) override;
virtual OwnPtr<Messages::WindowServer::SetWindowIconBitmapResponse> handle(const Messages::WindowServer::SetWindowIconBitmap&) override;
virtual void handle(const Messages::WindowServer::WM_SetWindowTaskbarRect&) override;
virtual OwnPtr<Messages::WindowServer::StartDragResponse> handle(const Messages::WindowServer::StartDrag&) override;
virtual OwnPtr<Messages::WindowServer::SetSystemMenuResponse> handle(const Messages::WindowServer::SetSystemMenu&) override;
virtual OwnPtr<Messages::WindowServer::SetSystemThemeResponse> handle(const Messages::WindowServer::SetSystemTheme&) override;
virtual OwnPtr<Messages::WindowServer::GetSystemThemeResponse> handle(const Messages::WindowServer::GetSystemTheme&) override;
virtual OwnPtr<Messages::WindowServer::SetWindowBaseSizeAndSizeIncrementResponse> handle(const Messages::WindowServer::SetWindowBaseSizeAndSizeIncrement&) override;
virtual void handle(const Messages::WindowServer::EnableDisplayLink&) override;
virtual void handle(const Messages::WindowServer::DisableDisplayLink&) override;
Window* window_from_id(i32 window_id);
HashMap<int, NonnullRefPtr<Window>> m_windows;
HashMap<int, NonnullOwnPtr<MenuBar>> m_menubars;
HashMap<int, NonnullRefPtr<Menu>> m_menus;
WeakPtr<MenuBar> m_app_menubar;
int m_next_menubar_id { 10000 };
int m_next_menu_id { 20000 };
int m_next_window_id { 1982 };
bool m_has_display_link { false };
RefPtr<SharedBuffer> m_last_sent_clipboard_content;
};
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <WindowServer/Clipboard.h>
namespace WindowServer {
Clipboard& Clipboard::the()
{
static Clipboard* s_the;
if (!s_the)
s_the = new Clipboard;
return *s_the;
}
Clipboard::Clipboard()
{
}
Clipboard::~Clipboard()
{
}
const u8* Clipboard::data() const
{
if (!m_shared_buffer)
return nullptr;
return (const u8*)m_shared_buffer->data();
}
int Clipboard::size() const
{
if (!m_shared_buffer)
return 0;
return m_contents_size;
}
void Clipboard::clear()
{
m_shared_buffer = nullptr;
m_contents_size = 0;
}
void Clipboard::set_data(NonnullRefPtr<SharedBuffer>&& data, int contents_size, const String& data_type)
{
dbg() << "Clipboard::set_data <- [" << data_type << "] " << data->data() << " (" << contents_size << " bytes)";
m_shared_buffer = move(data);
m_contents_size = contents_size;
m_data_type = data_type;
if (on_content_change)
on_content_change();
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/Function.h>
#include <AK/SharedBuffer.h>
#include <AK/String.h>
namespace WindowServer {
class Clipboard {
public:
static Clipboard& the();
~Clipboard();
bool has_data() const
{
return m_shared_buffer;
}
const String& data_type() const { return m_data_type; }
const u8* data() const;
int size() const;
void clear();
void set_data(NonnullRefPtr<SharedBuffer>&&, int contents_size, const String& data_type);
Function<void()> on_content_change;
private:
Clipboard();
String m_data_type;
RefPtr<SharedBuffer> m_shared_buffer;
int m_contents_size { 0 };
};
}

View file

@ -0,0 +1,507 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "Compositor.h"
#include "ClientConnection.h"
#include "Event.h"
#include "EventLoop.h"
#include "Screen.h"
#include "Window.h"
#include "WindowManager.h"
#include <AK/Memory.h>
#include <LibCore/Timer.h>
#include <LibGfx/Font.h>
#include <LibGfx/Painter.h>
#include <LibThread/BackgroundAction.h>
namespace WindowServer {
Compositor& Compositor::the()
{
static Compositor s_the;
return s_the;
}
WallpaperMode mode_to_enum(const String& name)
{
if (name == "simple")
return WallpaperMode::Simple;
if (name == "tile")
return WallpaperMode::Tile;
if (name == "center")
return WallpaperMode::Center;
if (name == "scaled")
return WallpaperMode::Scaled;
return WallpaperMode::Simple;
}
Compositor::Compositor()
{
m_display_link_notify_timer = add<Core::Timer>(
1000 / 60, [this] {
notify_display_links();
});
m_display_link_notify_timer->stop();
m_compose_timer = Core::Timer::create_single_shot(
1000 / 60,
[this] {
compose();
},
this);
m_immediate_compose_timer = Core::Timer::create_single_shot(
0,
[this] {
compose();
},
this);
m_screen_can_set_buffer = Screen::the().can_set_buffer();
init_bitmaps();
}
void Compositor::init_bitmaps()
{
auto& screen = Screen::the();
auto size = screen.size();
m_front_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGB32, size, screen.pitch(), screen.scanline(0));
if (m_screen_can_set_buffer)
m_back_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGB32, size, screen.pitch(), screen.scanline(size.height()));
else
m_back_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, size);
m_front_painter = make<Gfx::Painter>(*m_front_bitmap);
m_back_painter = make<Gfx::Painter>(*m_back_bitmap);
m_buffers_are_flipped = false;
invalidate();
}
void Compositor::compose()
{
auto& wm = WindowManager::the();
if (m_wallpaper_mode == WallpaperMode::Unchecked)
m_wallpaper_mode = mode_to_enum(wm.wm_config()->read_entry("Background", "Mode", "simple"));
auto& ws = Screen::the();
auto dirty_rects = move(m_dirty_rects);
if (dirty_rects.size() == 0) {
// nothing dirtied since the last compose pass.
return;
}
dirty_rects.add(Gfx::Rect::intersection(m_last_geometry_label_rect, Screen::the().rect()));
dirty_rects.add(Gfx::Rect::intersection(m_last_cursor_rect, Screen::the().rect()));
dirty_rects.add(Gfx::Rect::intersection(m_last_dnd_rect, Screen::the().rect()));
dirty_rects.add(Gfx::Rect::intersection(current_cursor_rect(), Screen::the().rect()));
auto any_dirty_rect_intersects_window = [&dirty_rects](const Window& window) {
auto window_frame_rect = window.frame().rect();
for (auto& dirty_rect : dirty_rects.rects()) {
if (dirty_rect.intersects(window_frame_rect))
return true;
}
return false;
};
Color background_color = wm.palette().desktop_background();
String background_color_entry = wm.wm_config()->read_entry("Background", "Color", "");
if (!background_color_entry.is_empty()) {
background_color = Color::from_string(background_color_entry).value_or(background_color);
}
// Paint the wallpaper.
for (auto& dirty_rect : dirty_rects.rects()) {
if (wm.any_opaque_window_contains_rect(dirty_rect))
continue;
// FIXME: If the wallpaper is opaque, no need to fill with color!
m_back_painter->fill_rect(dirty_rect, background_color);
if (m_wallpaper) {
if (m_wallpaper_mode == WallpaperMode::Simple) {
m_back_painter->blit(dirty_rect.location(), *m_wallpaper, dirty_rect);
} else if (m_wallpaper_mode == WallpaperMode::Center) {
Gfx::Point offset { ws.size().width() / 2 - m_wallpaper->size().width() / 2,
ws.size().height() / 2 - m_wallpaper->size().height() / 2 };
m_back_painter->blit_offset(dirty_rect.location(), *m_wallpaper,
dirty_rect, offset);
} else if (m_wallpaper_mode == WallpaperMode::Tile) {
m_back_painter->draw_tiled_bitmap(dirty_rect, *m_wallpaper);
} else if (m_wallpaper_mode == WallpaperMode::Scaled) {
float hscale = (float)m_wallpaper->size().width() / (float)ws.size().width();
float vscale = (float)m_wallpaper->size().height() / (float)ws.size().height();
m_back_painter->blit_scaled(dirty_rect, *m_wallpaper, dirty_rect, hscale, vscale);
} else {
ASSERT_NOT_REACHED();
}
}
}
auto compose_window = [&](Window& window) -> IterationDecision {
if (!any_dirty_rect_intersects_window(window))
return IterationDecision::Continue;
Gfx::PainterStateSaver saver(*m_back_painter);
m_back_painter->add_clip_rect(window.frame().rect());
RefPtr<Gfx::Bitmap> backing_store = window.backing_store();
for (auto& dirty_rect : dirty_rects.rects()) {
if (wm.any_opaque_window_above_this_one_contains_rect(window, dirty_rect))
continue;
Gfx::PainterStateSaver saver(*m_back_painter);
m_back_painter->add_clip_rect(dirty_rect);
if (!backing_store)
m_back_painter->fill_rect(dirty_rect, wm.palette().window());
if (!window.is_fullscreen())
window.frame().paint(*m_back_painter);
if (!backing_store)
continue;
// Decide where we would paint this window's backing store.
// This is subtly different from widow.rect(), because window
// size may be different from its backing store size. This
// happens when the window has been resized and the client
// has not yet attached a new backing store. In this case,
// we want to try to blit the backing store at the same place
// it was previously, and fill the rest of the window with its
// background color.
Gfx::Rect backing_rect;
backing_rect.set_size(backing_store->size());
switch (WindowManager::the().resize_direction_of_window(window)) {
case ResizeDirection::None:
case ResizeDirection::Right:
case ResizeDirection::Down:
case ResizeDirection::DownRight:
backing_rect.set_location(window.rect().location());
break;
case ResizeDirection::Left:
case ResizeDirection::Up:
case ResizeDirection::UpLeft:
backing_rect.set_right_without_resize(window.rect().right());
backing_rect.set_bottom_without_resize(window.rect().bottom());
break;
case ResizeDirection::UpRight:
backing_rect.set_left(window.rect().left());
backing_rect.set_bottom_without_resize(window.rect().bottom());
break;
case ResizeDirection::DownLeft:
backing_rect.set_right_without_resize(window.rect().right());
backing_rect.set_top(window.rect().top());
break;
}
Gfx::Rect dirty_rect_in_backing_coordinates = dirty_rect
.intersected(window.rect())
.intersected(backing_rect)
.translated(-backing_rect.location());
if (dirty_rect_in_backing_coordinates.is_empty())
continue;
auto dst = backing_rect.location().translated(dirty_rect_in_backing_coordinates.location());
m_back_painter->blit(dst, *backing_store, dirty_rect_in_backing_coordinates, window.opacity());
for (auto background_rect : window.rect().shatter(backing_rect))
m_back_painter->fill_rect(background_rect, wm.palette().window());
}
return IterationDecision::Continue;
};
// Paint the window stack.
if (auto* fullscreen_window = wm.active_fullscreen_window()) {
compose_window(*fullscreen_window);
} else {
wm.for_each_visible_window_from_back_to_front([&](Window& window) {
return compose_window(window);
});
draw_geometry_label();
}
run_animations();
draw_cursor();
if (m_flash_flush) {
for (auto& rect : dirty_rects.rects())
m_front_painter->fill_rect(rect, Color::Yellow);
}
if (m_screen_can_set_buffer)
flip_buffers();
for (auto& r : dirty_rects.rects())
flush(r);
}
void Compositor::flush(const Gfx::Rect& a_rect)
{
auto rect = Gfx::Rect::intersection(a_rect, Screen::the().rect());
Gfx::RGBA32* front_ptr = m_front_bitmap->scanline(rect.y()) + rect.x();
Gfx::RGBA32* back_ptr = m_back_bitmap->scanline(rect.y()) + rect.x();
size_t pitch = m_back_bitmap->pitch();
// NOTE: The meaning of a flush depends on whether we can flip buffers or not.
//
// If flipping is supported, flushing means that we've flipped, and now we
// copy the changed bits from the front buffer to the back buffer, to keep
// them in sync.
//
// If flipping is not supported, flushing means that we copy the changed
// rects from the backing bitmap to the display framebuffer.
Gfx::RGBA32* to_ptr;
const Gfx::RGBA32* from_ptr;
if (m_screen_can_set_buffer) {
to_ptr = back_ptr;
from_ptr = front_ptr;
} else {
to_ptr = front_ptr;
from_ptr = back_ptr;
}
for (int y = 0; y < rect.height(); ++y) {
fast_u32_copy(to_ptr, from_ptr, rect.width());
from_ptr = (const Gfx::RGBA32*)((const u8*)from_ptr + pitch);
to_ptr = (Gfx::RGBA32*)((u8*)to_ptr + pitch);
}
}
void Compositor::invalidate()
{
m_dirty_rects.clear_with_capacity();
invalidate(Screen::the().rect());
}
void Compositor::invalidate(const Gfx::Rect& a_rect)
{
auto rect = Gfx::Rect::intersection(a_rect, Screen::the().rect());
if (rect.is_empty())
return;
m_dirty_rects.add(rect);
// We delay composition by a timer interval, but to not affect latency too
// much, if a pending compose is not already scheduled, we also schedule an
// immediate compose the next spin of the event loop.
if (!m_compose_timer->is_active()) {
m_compose_timer->start();
m_immediate_compose_timer->start();
}
}
bool Compositor::set_background_color(const String& background_color)
{
auto& wm = WindowManager::the();
wm.wm_config()->write_entry("Background", "Color", background_color);
bool ret_val = wm.wm_config()->sync();
if (ret_val)
Compositor::invalidate();
return ret_val;
}
bool Compositor::set_wallpaper_mode(const String& mode)
{
auto& wm = WindowManager::the();
wm.wm_config()->write_entry("Background", "Mode", mode);
bool ret_val = wm.wm_config()->sync();
if (ret_val) {
m_wallpaper_mode = mode_to_enum(mode);
Compositor::invalidate();
}
return ret_val;
}
bool Compositor::set_wallpaper(const String& path, Function<void(bool)>&& callback)
{
LibThread::BackgroundAction<RefPtr<Gfx::Bitmap>>::create(
[path] {
return Gfx::Bitmap::load_from_file(path);
},
[this, path, callback = move(callback)](RefPtr<Gfx::Bitmap> bitmap) {
m_wallpaper_path = path;
m_wallpaper = move(bitmap);
invalidate();
callback(true);
});
return true;
}
void Compositor::flip_buffers()
{
ASSERT(m_screen_can_set_buffer);
swap(m_front_bitmap, m_back_bitmap);
swap(m_front_painter, m_back_painter);
Screen::the().set_buffer(m_buffers_are_flipped ? 0 : 1);
m_buffers_are_flipped = !m_buffers_are_flipped;
}
void Compositor::run_animations()
{
static const int minimize_animation_steps = 10;
WindowManager::the().for_each_window([&](Window& window) {
if (window.in_minimize_animation()) {
int animation_index = window.minimize_animation_index();
auto from_rect = window.is_minimized() ? window.frame().rect() : window.taskbar_rect();
auto to_rect = window.is_minimized() ? window.taskbar_rect() : window.frame().rect();
float x_delta_per_step = (float)(from_rect.x() - to_rect.x()) / minimize_animation_steps;
float y_delta_per_step = (float)(from_rect.y() - to_rect.y()) / minimize_animation_steps;
float width_delta_per_step = (float)(from_rect.width() - to_rect.width()) / minimize_animation_steps;
float height_delta_per_step = (float)(from_rect.height() - to_rect.height()) / minimize_animation_steps;
Gfx::Rect rect {
from_rect.x() - (int)(x_delta_per_step * animation_index),
from_rect.y() - (int)(y_delta_per_step * animation_index),
from_rect.width() - (int)(width_delta_per_step * animation_index),
from_rect.height() - (int)(height_delta_per_step * animation_index)
};
#ifdef MINIMIZE_ANIMATION_DEBUG
dbg() << "Minimize animation from " << from_rect << " to " << to_rect << " frame# " << animation_index << " " << rect;
#endif
m_back_painter->draw_rect(rect, Color::White);
window.step_minimize_animation();
if (window.minimize_animation_index() >= minimize_animation_steps)
window.end_minimize_animation();
invalidate(rect);
}
return IterationDecision::Continue;
});
}
bool Compositor::set_resolution(int desired_width, int desired_height)
{
auto screen_rect = Screen::the().rect();
if (screen_rect.width() == desired_width && screen_rect.height() == desired_height)
return true;
// Make sure it's impossible to set an invalid resolution
ASSERT(desired_width >= 640 && desired_height >= 480);
bool success = Screen::the().set_resolution(desired_width, desired_height);
init_bitmaps();
compose();
return success;
}
Gfx::Rect Compositor::current_cursor_rect() const
{
auto& wm = WindowManager::the();
return { Screen::the().cursor_location().translated(-wm.active_cursor().hotspot()), wm.active_cursor().size() };
}
void Compositor::invalidate_cursor()
{
auto& wm = WindowManager::the();
if (wm.dnd_client())
invalidate(wm.dnd_rect());
invalidate(current_cursor_rect());
}
void Compositor::draw_geometry_label()
{
auto& wm = WindowManager::the();
auto* window_being_moved_or_resized = wm.m_move_window ? wm.m_move_window.ptr() : (wm.m_resize_window ? wm.m_resize_window.ptr() : nullptr);
if (!window_being_moved_or_resized) {
m_last_geometry_label_rect = {};
return;
}
auto geometry_string = window_being_moved_or_resized->rect().to_string();
if (!window_being_moved_or_resized->size_increment().is_null()) {
int width_steps = (window_being_moved_or_resized->width() - window_being_moved_or_resized->base_size().width()) / window_being_moved_or_resized->size_increment().width();
int height_steps = (window_being_moved_or_resized->height() - window_being_moved_or_resized->base_size().height()) / window_being_moved_or_resized->size_increment().height();
geometry_string = String::format("%s (%dx%d)", geometry_string.characters(), width_steps, height_steps);
}
auto geometry_label_rect = Gfx::Rect { 0, 0, wm.font().width(geometry_string) + 16, wm.font().glyph_height() + 10 };
geometry_label_rect.center_within(window_being_moved_or_resized->rect());
m_back_painter->fill_rect(geometry_label_rect, wm.palette().window());
m_back_painter->draw_rect(geometry_label_rect, wm.palette().threed_shadow2());
m_back_painter->draw_text(geometry_label_rect, geometry_string, Gfx::TextAlignment::Center, wm.palette().window_text());
m_last_geometry_label_rect = geometry_label_rect;
}
void Compositor::draw_cursor()
{
auto& wm = WindowManager::the();
Gfx::Rect cursor_rect = current_cursor_rect();
m_back_painter->blit(cursor_rect.location(), wm.active_cursor().bitmap(), wm.active_cursor().rect());
if (wm.dnd_client()) {
auto dnd_rect = wm.dnd_rect();
m_back_painter->fill_rect(dnd_rect, wm.palette().selection().with_alpha(200));
if (!wm.dnd_text().is_empty()) {
auto text_rect = dnd_rect;
if (wm.dnd_bitmap())
text_rect.move_by(wm.dnd_bitmap()->width(), 0);
m_back_painter->draw_text(text_rect, wm.dnd_text(), Gfx::TextAlignment::CenterLeft, wm.palette().selection_text());
}
if (wm.dnd_bitmap()) {
m_back_painter->blit(dnd_rect.top_left(), *wm.dnd_bitmap(), wm.dnd_bitmap()->rect());
}
m_last_dnd_rect = dnd_rect;
} else {
m_last_dnd_rect = {};
}
m_last_cursor_rect = cursor_rect;
}
void Compositor::notify_display_links()
{
ClientConnection::for_each_client([](auto& client) {
client.notify_display_link({});
});
}
void Compositor::increment_display_link_count(Badge<ClientConnection>)
{
++m_display_link_count;
if (m_display_link_count == 1)
m_display_link_notify_timer->start();
}
void Compositor::decrement_display_link_count(Badge<ClientConnection>)
{
ASSERT(m_display_link_count);
--m_display_link_count;
if (!m_display_link_count)
m_display_link_notify_timer->stop();
}
}

View file

@ -0,0 +1,108 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/OwnPtr.h>
#include <AK/RefPtr.h>
#include <LibCore/Object.h>
#include <LibGfx/DisjointRectSet.h>
#include <LibGfx/Forward.h>
namespace WindowServer {
class ClientConnection;
class Cursor;
enum class WallpaperMode {
Simple,
Tile,
Center,
Scaled,
Unchecked
};
class Compositor final : public Core::Object {
C_OBJECT(Compositor)
public:
static Compositor& the();
void compose();
void invalidate();
void invalidate(const Gfx::Rect&);
bool set_resolution(int desired_width, int desired_height);
bool set_background_color(const String& background_color);
bool set_wallpaper_mode(const String& mode);
bool set_wallpaper(const String& path, Function<void(bool)>&& callback);
String wallpaper_path() const { return m_wallpaper_path; }
void invalidate_cursor();
Gfx::Rect current_cursor_rect() const;
void increment_display_link_count(Badge<ClientConnection>);
void decrement_display_link_count(Badge<ClientConnection>);
private:
Compositor();
void init_bitmaps();
void flip_buffers();
void flush(const Gfx::Rect&);
void draw_cursor();
void draw_geometry_label();
void draw_menubar();
void run_animations();
void notify_display_links();
RefPtr<Core::Timer> m_compose_timer;
RefPtr<Core::Timer> m_immediate_compose_timer;
bool m_flash_flush { false };
bool m_buffers_are_flipped { false };
bool m_screen_can_set_buffer { false };
RefPtr<Gfx::Bitmap> m_front_bitmap;
RefPtr<Gfx::Bitmap> m_back_bitmap;
OwnPtr<Gfx::Painter> m_back_painter;
OwnPtr<Gfx::Painter> m_front_painter;
Gfx::DisjointRectSet m_dirty_rects;
Gfx::Rect m_last_cursor_rect;
Gfx::Rect m_last_dnd_rect;
Gfx::Rect m_last_geometry_label_rect;
String m_wallpaper_path;
WallpaperMode m_wallpaper_mode { WallpaperMode::Unchecked };
RefPtr<Gfx::Bitmap> m_wallpaper;
RefPtr<Core::Timer> m_display_link_notify_timer;
size_t m_display_link_count { 0 };
};
}

View file

@ -0,0 +1,77 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <WindowServer/Cursor.h>
#include <WindowServer/WindowManager.h>
namespace WindowServer {
Cursor::Cursor(NonnullRefPtr<Gfx::Bitmap>&& bitmap, const Gfx::Point& hotspot)
: m_bitmap(move(bitmap))
, m_hotspot(hotspot)
{
}
Cursor::~Cursor()
{
}
NonnullRefPtr<Cursor> Cursor::create(NonnullRefPtr<Gfx::Bitmap>&& bitmap)
{
return adopt(*new Cursor(move(bitmap), bitmap->rect().center()));
}
NonnullRefPtr<Cursor> Cursor::create(NonnullRefPtr<Gfx::Bitmap>&& bitmap, const Gfx::Point& hotspot)
{
return adopt(*new Cursor(move(bitmap), hotspot));
}
RefPtr<Cursor> Cursor::create(StandardCursor standard_cursor)
{
switch (standard_cursor) {
case StandardCursor::None:
return nullptr;
case StandardCursor::Arrow:
return WindowManager::the().arrow_cursor();
case StandardCursor::IBeam:
return WindowManager::the().i_beam_cursor();
case StandardCursor::ResizeHorizontal:
return WindowManager::the().resize_horizontally_cursor();
case StandardCursor::ResizeVertical:
return WindowManager::the().resize_vertically_cursor();
case StandardCursor::ResizeDiagonalTLBR:
return WindowManager::the().resize_diagonally_tlbr_cursor();
case StandardCursor::ResizeDiagonalBLTR:
return WindowManager::the().resize_diagonally_bltr_cursor();
case StandardCursor::Hand:
return WindowManager::the().hand_cursor();
case StandardCursor::Drag:
return WindowManager::the().drag_cursor();
}
ASSERT_NOT_REACHED();
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <LibGfx/Bitmap.h>
namespace WindowServer {
enum class StandardCursor {
None = 0,
Arrow,
IBeam,
ResizeHorizontal,
ResizeVertical,
ResizeDiagonalTLBR,
ResizeDiagonalBLTR,
Hand,
Drag,
};
class Cursor : public RefCounted<Cursor> {
public:
static NonnullRefPtr<Cursor> create(NonnullRefPtr<Gfx::Bitmap>&&, const Gfx::Point& hotspot);
static NonnullRefPtr<Cursor> create(NonnullRefPtr<Gfx::Bitmap>&&);
static RefPtr<Cursor> create(StandardCursor);
~Cursor();
Gfx::Point hotspot() const { return m_hotspot; }
const Gfx::Bitmap& bitmap() const { return *m_bitmap; }
Gfx::Rect rect() const { return m_bitmap->rect(); }
Gfx::Size size() const { return m_bitmap->size(); }
private:
Cursor(NonnullRefPtr<Gfx::Bitmap>&&, const Gfx::Point&);
RefPtr<Gfx::Bitmap> m_bitmap;
Gfx::Point m_hotspot;
};
}

View file

@ -0,0 +1,157 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/String.h>
#include <Kernel/KeyCode.h>
#include <LibCore/Event.h>
#include <LibGfx/Rect.h>
#include <WindowServer/Cursor.h>
#include <WindowServer/WindowType.h>
namespace WindowServer {
class Event : public Core::Event {
public:
enum Type {
Invalid = 3000,
MouseMove,
MouseDown,
MouseDoubleClick,
MouseUp,
MouseWheel,
WindowEntered,
WindowLeft,
KeyDown,
KeyUp,
WindowActivated,
WindowDeactivated,
WindowCloseRequest,
WindowResized,
};
Event() {}
explicit Event(Type type)
: Core::Event(type)
{
}
virtual ~Event() {}
bool is_mouse_event() const { return type() == MouseMove || type() == MouseDown || type() == MouseDoubleClick || type() == MouseUp || type() == MouseWheel; }
bool is_key_event() const { return type() == KeyUp || type() == KeyDown; }
};
enum class MouseButton : u8 {
None = 0,
Left = 1,
Right = 2,
Middle = 4,
Back = 8,
Forward = 16,
};
class KeyEvent final : public Event {
public:
KeyEvent(Type type, int key, char character, u8 modifiers)
: Event(type)
, m_key(key)
, m_character(character)
, m_modifiers(modifiers)
{
}
int key() const { return m_key; }
bool ctrl() const { return m_modifiers & Mod_Ctrl; }
bool alt() const { return m_modifiers & Mod_Alt; }
bool shift() const { return m_modifiers & Mod_Shift; }
bool logo() const { return m_modifiers & Mod_Logo; }
u8 modifiers() const { return m_modifiers; }
char character() const { return m_character; }
private:
friend class EventLoop;
friend class Screen;
int m_key { 0 };
char m_character { 0 };
u8 m_modifiers { 0 };
};
class MouseEvent final : public Event {
public:
MouseEvent(Type type, const Gfx::Point& position, unsigned buttons, MouseButton button, unsigned modifiers, int wheel_delta = 0)
: Event(type)
, m_position(position)
, m_buttons(buttons)
, m_button(button)
, m_modifiers(modifiers)
, m_wheel_delta(wheel_delta)
{
}
Gfx::Point position() const { return m_position; }
int x() const { return m_position.x(); }
int y() const { return m_position.y(); }
MouseButton button() const { return m_button; }
unsigned buttons() const { return m_buttons; }
unsigned modifiers() const { return m_modifiers; }
int wheel_delta() const { return m_wheel_delta; }
bool is_drag() const { return m_drag; }
const String& drag_data_type() const { return m_drag_data_type; }
void set_drag(bool b) { m_drag = b; }
void set_drag_data_type(const String& drag_data_type) { m_drag_data_type = drag_data_type; }
MouseEvent translated(const Gfx::Point& delta) const { return MouseEvent((Type)type(), m_position.translated(delta), m_buttons, m_button, m_modifiers, m_wheel_delta); }
private:
Gfx::Point m_position;
unsigned m_buttons { 0 };
MouseButton m_button { MouseButton::None };
unsigned m_modifiers { 0 };
int m_wheel_delta { 0 };
bool m_drag { false };
String m_drag_data_type;
};
class ResizeEvent final : public Event {
public:
ResizeEvent(const Gfx::Rect& old_rect, const Gfx::Rect& rect)
: Event(Event::WindowResized)
, m_old_rect(old_rect)
, m_rect(rect)
{
}
Gfx::Rect old_rect() const { return m_old_rect; }
Gfx::Rect rect() const { return m_rect; }
private:
Gfx::Rect m_old_rect;
Gfx::Rect m_rect;
};
}

View file

@ -0,0 +1,157 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "Clipboard.h"
#include <Kernel/KeyCode.h>
#include <Kernel/MousePacket.h>
#include <LibCore/LocalSocket.h>
#include <LibCore/Object.h>
#include <WindowServer/ClientConnection.h>
#include <WindowServer/Cursor.h>
#include <WindowServer/Event.h>
#include <WindowServer/EventLoop.h>
#include <WindowServer/Screen.h>
#include <WindowServer/WindowManager.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
//#define WSMESSAGELOOP_DEBUG
namespace WindowServer {
EventLoop::EventLoop()
: m_server(Core::LocalServer::construct())
{
m_keyboard_fd = open("/dev/keyboard", O_RDONLY | O_NONBLOCK | O_CLOEXEC);
m_mouse_fd = open("/dev/mouse", O_RDONLY | O_NONBLOCK | O_CLOEXEC);
bool ok = m_server->take_over_from_system_server();
ASSERT(ok);
m_server->on_ready_to_accept = [this] {
auto client_socket = m_server->accept();
if (!client_socket) {
dbg() << "WindowServer: accept failed.";
return;
}
static int s_next_client_id = 0;
int client_id = ++s_next_client_id;
IPC::new_client_connection<ClientConnection>(*client_socket, client_id);
};
ASSERT(m_keyboard_fd >= 0);
ASSERT(m_mouse_fd >= 0);
m_keyboard_notifier = Core::Notifier::construct(m_keyboard_fd, Core::Notifier::Read);
m_keyboard_notifier->on_ready_to_read = [this] { drain_keyboard(); };
m_mouse_notifier = Core::Notifier::construct(m_mouse_fd, Core::Notifier::Read);
m_mouse_notifier->on_ready_to_read = [this] { drain_mouse(); };
Clipboard::the().on_content_change = [&] {
ClientConnection::for_each_client([&](auto& client) {
client.notify_about_clipboard_contents_changed();
});
};
}
EventLoop::~EventLoop()
{
}
void EventLoop::drain_mouse()
{
auto& screen = Screen::the();
MousePacket state;
state.buttons = screen.mouse_button_state();
unsigned buttons = state.buttons;
MousePacket packets[32];
ssize_t nread = read(m_mouse_fd, &packets, sizeof(packets));
if (nread < 0) {
perror("EventLoop::drain_mouse read");
return;
}
size_t npackets = nread / sizeof(MousePacket);
if (!npackets)
return;
for (size_t i = 0; i < npackets; ++i) {
auto& packet = packets[i];
#ifdef WSMESSAGELOOP_DEBUG
dbgprintf("EventLoop: Mouse X %d, Y %d, Z %d, relative %d\n", packet.x, packet.y, packet.z, packet.is_relative);
#endif
buttons = packet.buttons;
state.is_relative = packet.is_relative;
if (packet.is_relative) {
state.x += packet.x;
state.y -= packet.y;
state.z += packet.z;
} else {
state.x = packet.x;
state.y = packet.y;
state.z += packet.z;
}
if (buttons != state.buttons) {
state.buttons = buttons;
#ifdef WSMESSAGELOOP_DEBUG
dbgprintf("EventLoop: Mouse Button Event\n");
#endif
screen.on_receive_mouse_data(state);
if (state.is_relative) {
state.x = 0;
state.y = 0;
state.z = 0;
}
}
}
if (state.is_relative && (state.x || state.y || state.z))
screen.on_receive_mouse_data(state);
if (!state.is_relative)
screen.on_receive_mouse_data(state);
}
void EventLoop::drain_keyboard()
{
auto& screen = Screen::the();
for (;;) {
::KeyEvent event;
ssize_t nread = read(m_keyboard_fd, (u8*)&event, sizeof(::KeyEvent));
if (nread == 0)
break;
ASSERT(nread == sizeof(::KeyEvent));
screen.on_receive_keyboard_data(event);
}
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <LibCore/EventLoop.h>
#include <LibCore/LocalServer.h>
#include <LibCore/Notifier.h>
namespace WindowServer {
class ClientConnection;
class EventLoop {
public:
EventLoop();
virtual ~EventLoop();
int exec() { return m_event_loop.exec(); }
private:
void drain_mouse();
void drain_keyboard();
Core::EventLoop m_event_loop;
int m_keyboard_fd { -1 };
RefPtr<Core::Notifier> m_keyboard_notifier;
int m_mouse_fd { -1 };
RefPtr<Core::Notifier> m_mouse_notifier;
RefPtr<Core::LocalServer> m_server;
};
}

View file

@ -0,0 +1,38 @@
OBJS = \
AppletManager.o \
Button.o \
ClientConnection.o \
Clipboard.o \
Compositor.o \
Cursor.o \
EventLoop.o \
Menu.o \
MenuBar.o \
MenuItem.o \
MenuManager.o \
Screen.o \
Window.o \
WindowFrame.o \
WindowManager.o \
WindowSwitcher.o \
main.o
PROGRAM = WindowServer
LIB_DEPS = Gfx Core Thread Pthread IPC
*.cpp: WindowServerEndpoint.h WindowClientEndpoint.h
WindowServerEndpoint.h: WindowServer.ipc | IPCCOMPILER
@echo "IPC $<"; $(IPCCOMPILER) $< > $@
WindowClientEndpoint.h: WindowClient.ipc | IPCCOMPILER
@echo "IPC $<"; $(IPCCOMPILER) $< > $@
EXTRA_CLEAN = WindowServerEndpoint.h WindowClientEndpoint.h
install:
mkdir -p ../../Root/usr/include/WindowServer/
cp *.h ../../Root/usr/include/WindowServer/
include ../../Makefile.common

View file

@ -0,0 +1,570 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020, Shannon Booth <shannon.ml.booth@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "Menu.h"
#include "Event.h"
#include "EventLoop.h"
#include "MenuItem.h"
#include "MenuManager.h"
#include "Screen.h"
#include "Window.h"
#include "WindowManager.h"
#include <LibGfx/Bitmap.h>
#include <LibGfx/CharacterBitmap.h>
#include <LibGfx/Font.h>
#include <LibGfx/Painter.h>
#include <LibGfx/StylePainter.h>
#include <LibGfx/Triangle.h>
#include <WindowServer/ClientConnection.h>
#include <WindowServer/WindowClientEndpoint.h>
namespace WindowServer {
Menu::Menu(ClientConnection* client, int menu_id, const String& name)
: Core::Object(client)
, m_client(client)
, m_menu_id(menu_id)
, m_name(move(name))
{
}
Menu::~Menu()
{
}
void Menu::set_title_font(const Gfx::Font& font)
{
m_title_font = &font;
}
const Gfx::Font& Menu::title_font() const
{
return *m_title_font;
}
const Gfx::Font& Menu::font() const
{
return Gfx::Font::default_font();
}
static const char* s_checked_bitmap_data = {
" "
" # "
" ## "
" ### "
" ## ### "
" ##### "
" ### "
" # "
" "
};
static const char* s_submenu_arrow_bitmap_data = {
" "
" # "
" ## "
" ### "
" #### "
" ### "
" ## "
" # "
" "
};
static Gfx::CharacterBitmap* s_checked_bitmap;
static const int s_checked_bitmap_width = 9;
static const int s_checked_bitmap_height = 9;
static const int s_submenu_arrow_bitmap_width = 9;
static const int s_submenu_arrow_bitmap_height = 9;
static const int s_item_icon_width = 16;
static const int s_stripe_width = 23;
int Menu::content_width() const
{
int widest_text = 0;
int widest_shortcut = 0;
for (auto& item : m_items) {
if (item.type() != MenuItem::Text)
continue;
int text_width = font().width(item.text());
if (!item.shortcut_text().is_empty()) {
int shortcut_width = font().width(item.shortcut_text());
widest_shortcut = max(shortcut_width, widest_shortcut);
}
widest_text = max(widest_text, text_width);
}
int widest_item = widest_text + s_stripe_width;
if (widest_shortcut)
widest_item += padding_between_text_and_shortcut() + widest_shortcut;
return max(widest_item, rect_in_menubar().width()) + horizontal_padding() + frame_thickness() * 2;
}
void Menu::redraw()
{
if (!menu_window())
return;
draw();
menu_window()->invalidate();
}
Window& Menu::ensure_menu_window()
{
if (m_menu_window)
return *m_menu_window;
int width = this->content_width();
Gfx::Point next_item_location(frame_thickness(), frame_thickness());
for (auto& item : m_items) {
int height = 0;
if (item.type() == MenuItem::Text)
height = item_height();
else if (item.type() == MenuItem::Separator)
height = 8;
item.set_rect({ next_item_location, { width - frame_thickness() * 2, height } });
next_item_location.move_by(0, height);
}
int window_height_available = Screen::the().height() - MenuManager::the().menubar_rect().height() - frame_thickness() * 2;
int max_window_height = (window_height_available / item_height()) * item_height() + frame_thickness() * 2;
int content_height = m_items.is_empty() ? 0 : (m_items.last().rect().bottom() + 1) + frame_thickness();
int window_height = min(max_window_height, content_height);
if (window_height < content_height) {
m_scrollable = true;
m_max_scroll_offset = item_count() - window_height / item_height() + 2;
}
auto window = Window::construct(*this, WindowType::Menu);
window->set_rect(0, 0, width, window_height);
m_menu_window = move(window);
draw();
return *m_menu_window;
}
int Menu::visible_item_count() const
{
if (!is_scrollable())
return m_items.size();
ASSERT(m_menu_window);
// Make space for up/down arrow indicators
return m_menu_window->height() / item_height() - 2;
}
void Menu::draw()
{
auto palette = WindowManager::the().palette();
m_theme_index_at_last_paint = MenuManager::the().theme_index();
ASSERT(menu_window());
ASSERT(menu_window()->backing_store());
Gfx::Painter painter(*menu_window()->backing_store());
Gfx::Rect rect { {}, menu_window()->size() };
Gfx::StylePainter::paint_window_frame(painter, rect, palette);
painter.fill_rect(rect.shrunken(6, 6), palette.menu_base());
int width = this->content_width();
if (!s_checked_bitmap)
s_checked_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_checked_bitmap_data, s_checked_bitmap_width, s_checked_bitmap_height).leak_ref();
bool has_checkable_items = false;
bool has_items_with_icon = false;
for (auto& item : m_items) {
has_checkable_items = has_checkable_items | item.is_checkable();
has_items_with_icon = has_items_with_icon | !!item.icon();
}
Gfx::Rect stripe_rect { frame_thickness(), frame_thickness(), s_stripe_width, menu_window()->height() - frame_thickness() * 2 };
painter.fill_rect(stripe_rect, palette.menu_stripe());
painter.draw_line(stripe_rect.top_right(), stripe_rect.bottom_right(), palette.menu_stripe().darkened());
int visible_item_count = this->visible_item_count();
if (is_scrollable()) {
bool can_go_up = m_scroll_offset > 0;
bool can_go_down = m_scroll_offset < m_max_scroll_offset;
Gfx::Rect up_indicator_rect { frame_thickness(), frame_thickness(), content_width(), item_height() };
painter.draw_text(up_indicator_rect, "\xc3\xb6", Gfx::TextAlignment::Center, can_go_up ? palette.menu_base_text() : palette.color(ColorRole::DisabledText));
Gfx::Rect down_indicator_rect { frame_thickness(), menu_window()->height() - item_height() - frame_thickness(), content_width(), item_height() };
painter.draw_text(down_indicator_rect, "\xc3\xb7", Gfx::TextAlignment::Center, can_go_down ? palette.menu_base_text() : palette.color(ColorRole::DisabledText));
}
for (int i = 0; i < visible_item_count; ++i) {
auto& item = m_items.at(m_scroll_offset + i);
if (item.type() == MenuItem::Text) {
Color text_color = palette.menu_base_text();
if (&item == hovered_item() && item.is_enabled()) {
painter.fill_rect(item.rect(), palette.menu_selection());
painter.draw_rect(item.rect(), palette.menu_selection().darkened());
text_color = palette.menu_selection_text();
} else if (!item.is_enabled()) {
text_color = Color::MidGray;
}
Gfx::Rect text_rect = item.rect().translated(stripe_rect.width() + 6, 0);
if (item.is_checkable()) {
if (item.is_exclusive()) {
Gfx::Rect radio_rect { item.rect().x() + 5, 0, 12, 12 };
radio_rect.center_vertically_within(text_rect);
Gfx::StylePainter::paint_radio_button(painter, radio_rect, palette, item.is_checked(), false);
} else {
Gfx::Rect checkmark_rect { item.rect().x() + 7, 0, s_checked_bitmap_width, s_checked_bitmap_height };
checkmark_rect.center_vertically_within(text_rect);
Gfx::Rect checkbox_rect = checkmark_rect.inflated(4, 4);
painter.fill_rect(checkbox_rect, palette.base());
Gfx::StylePainter::paint_frame(painter, checkbox_rect, palette, Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
if (item.is_checked()) {
painter.draw_bitmap(checkmark_rect.location(), *s_checked_bitmap, palette.button_text());
}
}
} else if (item.icon()) {
Gfx::Rect icon_rect { item.rect().x() + 3, 0, s_item_icon_width, s_item_icon_width };
icon_rect.center_vertically_within(text_rect);
painter.blit(icon_rect.location(), *item.icon(), item.icon()->rect());
}
painter.draw_text(text_rect, item.text(), Gfx::TextAlignment::CenterLeft, text_color);
if (!item.shortcut_text().is_empty()) {
painter.draw_text(item.rect().translated(-right_padding(), 0), item.shortcut_text(), Gfx::TextAlignment::CenterRight, text_color);
}
if (item.is_submenu()) {
static auto& submenu_arrow_bitmap = Gfx::CharacterBitmap::create_from_ascii(s_submenu_arrow_bitmap_data, s_submenu_arrow_bitmap_width, s_submenu_arrow_bitmap_height).leak_ref();
Gfx::Rect submenu_arrow_rect {
item.rect().right() - s_submenu_arrow_bitmap_width - 2,
0,
s_submenu_arrow_bitmap_width,
s_submenu_arrow_bitmap_height
};
submenu_arrow_rect.center_vertically_within(item.rect());
painter.draw_bitmap(submenu_arrow_rect.location(), submenu_arrow_bitmap, text_color);
}
} else if (item.type() == MenuItem::Separator) {
Gfx::Point p1(item.rect().translated(stripe_rect.width() + 4, 0).x(), item.rect().center().y() - 1);
Gfx::Point p2(width - 7, item.rect().center().y() - 1);
painter.draw_line(p1, p2, palette.threed_shadow1());
painter.draw_line(p1.translated(0, 1), p2.translated(0, 1), palette.threed_highlight());
}
}
}
MenuItem* Menu::hovered_item() const
{
if (m_hovered_item_index == -1)
return nullptr;
return const_cast<MenuItem*>(&item(m_hovered_item_index));
}
void Menu::update_for_new_hovered_item()
{
if (hovered_item() && hovered_item()->is_submenu()) {
MenuManager::the().close_everyone_not_in_lineage(*hovered_item()->submenu());
hovered_item()->submenu()->popup(hovered_item()->rect().top_right().translated(menu_window()->rect().location()), true);
} else {
MenuManager::the().close_everyone_not_in_lineage(*this);
MenuManager::the().set_current_menu(this);
menu_window()->set_visible(true);
}
redraw();
}
void Menu::open_hovered_item()
{
ASSERT(menu_window());
ASSERT(menu_window()->is_visible());
if (!hovered_item())
return;
if (hovered_item()->is_enabled())
did_activate(*hovered_item());
clear_hovered_item();
}
void Menu::descend_into_submenu_at_hovered_item()
{
ASSERT(hovered_item());
ASSERT(hovered_item()->is_submenu());
auto submenu = hovered_item()->submenu();
submenu->m_hovered_item_index = 0;
ASSERT(submenu->hovered_item()->type() != MenuItem::Separator);
submenu->update_for_new_hovered_item();
m_in_submenu = true;
}
void Menu::handle_mouse_move_event(const MouseEvent& mouse_event)
{
ASSERT(menu_window());
if (hovered_item() && hovered_item()->is_submenu()) {
auto item = *hovered_item();
auto submenu_top_left = item.rect().location() + Gfx::Point { item.rect().width(), 0 };
auto submenu_bottom_left = submenu_top_left + Gfx::Point { 0, item.submenu()->menu_window()->height() };
auto safe_hover_triangle = Gfx::Triangle { m_last_position_in_hover, submenu_top_left, submenu_bottom_left };
m_last_position_in_hover = mouse_event.position();
// Don't update the hovered item if mouse is moving towards a submenu
if (safe_hover_triangle.contains(mouse_event.position()))
return;
}
int index = item_index_at(mouse_event.position());
if (m_hovered_item_index == index)
return;
m_hovered_item_index = index;
// FIXME: Tell parent menu (if it exists) that it is currently in a submenu
m_in_submenu = false;
update_for_new_hovered_item();
return;
}
void Menu::event(Core::Event& event)
{
if (event.type() == Event::MouseMove) {
handle_mouse_move_event(static_cast<const MouseEvent&>(event));
return;
}
if (event.type() == Event::MouseUp) {
open_hovered_item();
return;
}
if (event.type() == Event::MouseWheel && is_scrollable()) {
ASSERT(menu_window());
auto& mouse_event = static_cast<const MouseEvent&>(event);
m_scroll_offset += mouse_event.wheel_delta();
m_scroll_offset = clamp(m_scroll_offset, 0, m_max_scroll_offset);
int index = item_index_at(mouse_event.position());
if (m_hovered_item_index == index)
return;
m_hovered_item_index = index;
update_for_new_hovered_item();
return;
}
if (event.type() == Event::KeyDown) {
auto key = static_cast<KeyEvent&>(event).key();
if (!(key == Key_Up || key == Key_Down || key == Key_Left || key == Key_Right || key == Key_Return))
return;
ASSERT(menu_window());
ASSERT(menu_window()->is_visible());
// Default to the first item on key press if one has not been selected yet
if (!hovered_item()) {
m_hovered_item_index = 0;
update_for_new_hovered_item();
return;
}
// Pass the event for the submenu that we are currently in to handle
if (m_in_submenu && key != Key_Left) {
ASSERT(hovered_item()->is_submenu());
hovered_item()->submenu()->dispatch_event(event);
return;
}
if (key == Key_Return) {
if (!hovered_item()->is_enabled())
return;
if (hovered_item()->is_submenu())
descend_into_submenu_at_hovered_item();
else
open_hovered_item();
return;
}
if (key == Key_Up) {
ASSERT(m_items.at(0).type() != MenuItem::Separator);
if (is_scrollable() && m_hovered_item_index == 0)
return;
auto original_index = m_hovered_item_index;
do {
if (m_hovered_item_index == 0)
m_hovered_item_index = m_items.size() - 1;
else
--m_hovered_item_index;
if (m_hovered_item_index == original_index)
return;
} while (hovered_item()->type() == MenuItem::Separator || !hovered_item()->is_enabled());
ASSERT(m_hovered_item_index >= 0 && m_hovered_item_index <= static_cast<int>(m_items.size()) - 1);
if (is_scrollable() && m_hovered_item_index < m_scroll_offset)
--m_scroll_offset;
update_for_new_hovered_item();
return;
}
if (key == Key_Down) {
ASSERT(m_items.at(0).type() != MenuItem::Separator);
if (is_scrollable() && m_hovered_item_index == static_cast<int>(m_items.size()) - 1)
return;
auto original_index = m_hovered_item_index;
do {
if (m_hovered_item_index == static_cast<int>(m_items.size()) - 1)
m_hovered_item_index = 0;
else
++m_hovered_item_index;
if (m_hovered_item_index == original_index)
return;
} while (hovered_item()->type() == MenuItem::Separator || !hovered_item()->is_enabled());
ASSERT(m_hovered_item_index >= 0 && m_hovered_item_index <= static_cast<int>(m_items.size()) - 1);
if (is_scrollable() && m_hovered_item_index >= (m_scroll_offset + visible_item_count()))
++m_scroll_offset;
update_for_new_hovered_item();
return;
}
if (key == Key_Left) {
if (!m_in_submenu)
return;
ASSERT(hovered_item()->is_submenu());
hovered_item()->submenu()->clear_hovered_item();
m_in_submenu = false;
return;
}
if (key == Key_Right) {
if (hovered_item()->is_enabled() && hovered_item()->is_submenu())
descend_into_submenu_at_hovered_item();
return;
}
}
Core::Object::event(event);
}
void Menu::clear_hovered_item()
{
if (!hovered_item())
return;
m_hovered_item_index = -1;
m_in_submenu = false;
redraw();
}
void Menu::did_activate(MenuItem& item)
{
if (item.type() == MenuItem::Type::Separator)
return;
if (on_item_activation)
on_item_activation(item);
MenuManager::the().close_bar();
if (m_client)
m_client->post_message(Messages::WindowClient::MenuItemActivated(m_menu_id, item.identifier()));
}
MenuItem* Menu::item_with_identifier(unsigned identifer)
{
for (auto& item : m_items) {
if (item.identifier() == identifer)
return &item;
}
return nullptr;
}
int Menu::item_index_at(const Gfx::Point& position)
{
int i = 0;
for (auto& item : m_items) {
if (item.rect().contains(position))
return i;
++i;
}
return -1;
}
void Menu::close()
{
MenuManager::the().close_menu_and_descendants(*this);
}
void Menu::redraw_if_theme_changed()
{
if (m_theme_index_at_last_paint != MenuManager::the().theme_index())
redraw();
}
void Menu::popup(const Gfx::Point& position, bool is_submenu)
{
if (is_empty()) {
dbg() << "Menu: Empty menu popup";
return;
}
auto& window = ensure_menu_window();
redraw_if_theme_changed();
const int margin = 30;
Gfx::Point adjusted_pos = position;
if (adjusted_pos.x() + window.width() >= Screen::the().width() - margin) {
adjusted_pos = adjusted_pos.translated(-window.width(), 0);
}
if (adjusted_pos.y() + window.height() >= Screen::the().height() - margin) {
adjusted_pos = adjusted_pos.translated(0, -window.height());
}
if (adjusted_pos.y() < MenuManager::the().menubar_rect().height())
adjusted_pos.set_y(MenuManager::the().menubar_rect().height());
window.move_to(adjusted_pos);
window.set_visible(true);
MenuManager::the().set_current_menu(this, is_submenu);
}
bool Menu::is_menu_ancestor_of(const Menu& other) const
{
for (auto& item : m_items) {
if (!item.is_submenu())
continue;
auto& submenu = *const_cast<MenuItem&>(item).submenu();
if (&submenu == &other)
return true;
if (submenu.is_menu_ancestor_of(other))
return true;
}
return false;
}
}

View file

@ -0,0 +1,158 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/NonnullOwnPtrVector.h>
#include <AK/String.h>
#include <AK/WeakPtr.h>
#include <LibCore/Object.h>
#include <LibGfx/Font.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Rect.h>
#include <WindowServer/Cursor.h>
#include <WindowServer/MenuItem.h>
#include <WindowServer/Window.h>
namespace WindowServer {
class ClientConnection;
class MenuBar;
class Event;
class Menu final : public Core::Object {
C_OBJECT(Menu)
public:
Menu(ClientConnection*, int menu_id, const String& name);
virtual ~Menu() override;
ClientConnection* client() { return m_client; }
const ClientConnection* client() const { return m_client; }
int menu_id() const { return m_menu_id; }
MenuBar* menubar() { return m_menubar; }
const MenuBar* menubar() const { return m_menubar; }
void set_menubar(MenuBar* menubar) { m_menubar = menubar; }
bool is_empty() const { return m_items.is_empty(); }
int item_count() const { return m_items.size(); }
const MenuItem& item(int index) const { return m_items.at(index); }
MenuItem& item(int index) { return m_items.at(index); }
void add_item(NonnullOwnPtr<MenuItem>&& item) { m_items.append(move(item)); }
String name() const { return m_name; }
template<typename Callback>
void for_each_item(Callback callback) const
{
for (auto& item : m_items)
callback(item);
}
Gfx::Rect text_rect_in_menubar() const { return m_text_rect_in_menubar; }
void set_text_rect_in_menubar(const Gfx::Rect& rect) { m_text_rect_in_menubar = rect; }
Gfx::Rect rect_in_menubar() const { return m_rect_in_menubar; }
void set_rect_in_menubar(const Gfx::Rect& rect) { m_rect_in_menubar = rect; }
Window* menu_window() { return m_menu_window.ptr(); }
Window& ensure_menu_window();
Window* window_menu_of() { return m_window_menu_of; }
void set_window_menu_of(Window& window) { m_window_menu_of = window.make_weak_ptr(); }
bool is_window_menu_open() { return m_is_window_menu_open; }
void set_window_menu_open(bool is_open) { m_is_window_menu_open = is_open; }
int content_width() const;
int item_height() const { return 20; }
int frame_thickness() const { return 3; }
int horizontal_padding() const { return left_padding() + right_padding(); }
int left_padding() const { return 14; }
int right_padding() const { return 14; }
void draw();
const Gfx::Font& font() const;
const Gfx::Font& title_font() const;
void set_title_font(const Gfx::Font& font);
MenuItem* item_with_identifier(unsigned);
void redraw();
MenuItem* hovered_item() const;
void clear_hovered_item();
Function<void(MenuItem&)> on_item_activation;
void close();
void popup(const Gfx::Point&, bool is_submenu = false);
bool is_menu_ancestor_of(const Menu&) const;
void redraw_if_theme_changed();
bool is_scrollable() const { return m_scrollable; }
int scroll_offset() const { return m_scroll_offset; }
private:
virtual void event(Core::Event&) override;
RefPtr<Gfx::Font> m_title_font { &Gfx::Font::default_font() };
void handle_mouse_move_event(const MouseEvent&);
int visible_item_count() const;
int item_index_at(const Gfx::Point&);
int padding_between_text_and_shortcut() const { return 50; }
void did_activate(MenuItem&);
void open_hovered_item();
void update_for_new_hovered_item();
void descend_into_submenu_at_hovered_item();
ClientConnection* m_client { nullptr };
int m_menu_id { 0 };
String m_name;
Gfx::Rect m_rect_in_menubar;
Gfx::Rect m_text_rect_in_menubar;
MenuBar* m_menubar { nullptr };
NonnullOwnPtrVector<MenuItem> m_items;
RefPtr<Window> m_menu_window;
WeakPtr<Window> m_window_menu_of;
bool m_is_window_menu_open = { false };
Gfx::Point m_last_position_in_hover;
int m_theme_index_at_last_paint { -1 };
int m_hovered_item_index { -1 };
bool m_in_submenu { false };
bool m_scrollable { false };
int m_scroll_offset { 0 };
int m_max_scroll_offset { 0 };
};
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "MenuBar.h"
#include "Menu.h"
#include "MenuItem.h"
#include <LibGfx/Bitmap.h>
namespace WindowServer {
MenuBar::MenuBar(ClientConnection& client, int menubar_id)
: m_client(client)
, m_menubar_id(menubar_id)
{
}
MenuBar::~MenuBar()
{
}
void MenuBar::add_menu(Menu& menu)
{
menu.set_menubar(this);
// NOTE: We assume that the first menu is the App menu, which has a bold font.
if (m_menus.is_empty())
menu.set_title_font(Gfx::Font::default_bold_font());
m_menus.append(&menu);
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include "Menu.h"
#include <AK/Vector.h>
#include <AK/WeakPtr.h>
#include <AK/Weakable.h>
namespace WindowServer {
class MenuBar : public Weakable<MenuBar> {
public:
MenuBar(ClientConnection& client, int menubar_id);
~MenuBar();
ClientConnection& client() { return m_client; }
const ClientConnection& client() const { return m_client; }
int menubar_id() const { return m_menubar_id; }
void add_menu(Menu&);
template<typename Callback>
void for_each_menu(Callback callback)
{
for (auto& menu : m_menus) {
if (callback(*menu) == IterationDecision::Break)
return;
}
}
private:
ClientConnection& m_client;
int m_menubar_id { 0 };
Vector<Menu*> m_menus;
};
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "MenuItem.h"
#include "ClientConnection.h"
#include "Menu.h"
#include "WindowManager.h"
#include <LibGfx/Bitmap.h>
namespace WindowServer {
MenuItem::MenuItem(Menu& menu, unsigned identifier, const String& text, const String& shortcut_text, bool enabled, bool checkable, bool checked, const Gfx::Bitmap* icon)
: m_menu(menu)
, m_type(Text)
, m_enabled(enabled)
, m_checkable(checkable)
, m_checked(checked)
, m_identifier(identifier)
, m_text(text)
, m_shortcut_text(shortcut_text)
, m_icon(icon)
{
}
MenuItem::MenuItem(Menu& menu, Type type)
: m_menu(menu)
, m_type(type)
{
}
MenuItem::~MenuItem()
{
}
void MenuItem::set_enabled(bool enabled)
{
if (m_enabled == enabled)
return;
m_enabled = enabled;
m_menu.redraw();
}
void MenuItem::set_checked(bool checked)
{
if (m_checked == checked)
return;
m_checked = checked;
m_menu.redraw();
}
Menu* MenuItem::submenu()
{
ASSERT(is_submenu());
ASSERT(m_menu.client());
return m_menu.client()->find_menu_by_id(m_submenu_id);
}
Gfx::Rect MenuItem::rect() const
{
if (!m_menu.is_scrollable())
return m_rect;
return m_rect.translated(0, m_menu.item_height() - (m_menu.scroll_offset() * m_menu.item_height()));
}
void MenuItem::set_icon(const Gfx::Bitmap* icon)
{
if (m_icon == icon)
return;
m_icon = icon;
m_menu.redraw();
}
}

View file

@ -0,0 +1,99 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/Function.h>
#include <AK/String.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Rect.h>
namespace WindowServer {
class Menu;
class MenuItem {
public:
enum Type {
None,
Text,
Separator,
};
MenuItem(Menu&, unsigned identifier, const String& text, const String& shortcut_text = {}, bool enabled = true, bool checkable = false, bool checked = false, const Gfx::Bitmap* icon = nullptr);
MenuItem(Menu&, Type);
~MenuItem();
Type type() const { return m_type; }
bool is_enabled() const { return m_enabled; }
void set_enabled(bool);
bool is_checkable() const { return m_checkable; }
void set_checkable(bool checkable) { m_checkable = checkable; }
bool is_checked() const { return m_checked; }
void set_checked(bool);
String text() const { return m_text; }
void set_text(const String& text) { m_text = text; }
String shortcut_text() const { return m_shortcut_text; }
void set_shortcut_text(const String& text) { m_shortcut_text = text; }
void set_rect(const Gfx::Rect& rect) { m_rect = rect; }
Gfx::Rect rect() const;
unsigned identifier() const { return m_identifier; }
const Gfx::Bitmap* icon() const { return m_icon; }
void set_icon(const Gfx::Bitmap*);
bool is_submenu() const { return m_submenu_id != -1; }
int submenu_id() const { return m_submenu_id; }
void set_submenu_id(int submenu_id) { m_submenu_id = submenu_id; }
Menu* submenu();
bool is_exclusive() const { return m_exclusive; }
void set_exclusive(bool exclusive) { m_exclusive = exclusive; }
private:
Menu& m_menu;
Type m_type { None };
bool m_enabled { true };
bool m_checkable { false };
bool m_checked { false };
unsigned m_identifier { 0 };
String m_text;
String m_shortcut_text;
Gfx::Rect m_rect;
RefPtr<Gfx::Bitmap> m_icon;
int m_submenu_id { -1 };
bool m_exclusive { false };
};
}

View file

@ -0,0 +1,404 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020, Shannon Booth <shannon.ml.booth@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <AK/Badge.h>
#include <AK/FileSystemPath.h>
#include <AK/QuickSort.h>
#include <LibCore/DirIterator.h>
#include <LibGfx/Font.h>
#include <LibGfx/Painter.h>
#include <WindowServer/AppletManager.h>
#include <WindowServer/MenuManager.h>
#include <WindowServer/Screen.h>
#include <WindowServer/WindowManager.h>
#include <unistd.h>
//#define DEBUG_MENUS
namespace WindowServer {
static MenuManager* s_the;
MenuManager& MenuManager::the()
{
ASSERT(s_the);
return *s_the;
}
MenuManager::MenuManager()
{
s_the = this;
m_needs_window_resize = true;
// NOTE: This ensures that the system menu has the correct dimensions.
set_current_menubar(nullptr);
m_window = Window::construct(*this, WindowType::Menubar);
m_window->set_rect(menubar_rect());
}
MenuManager::~MenuManager()
{
}
bool MenuManager::is_open(const Menu& menu) const
{
for (size_t i = 0; i < m_open_menu_stack.size(); ++i) {
if (&menu == m_open_menu_stack[i].ptr())
return true;
}
return false;
}
void MenuManager::draw()
{
auto& wm = WindowManager::the();
auto palette = wm.palette();
auto menubar_rect = this->menubar_rect();
if (m_needs_window_resize) {
m_window->set_rect(menubar_rect);
AppletManager::the().calculate_applet_rects(window());
m_needs_window_resize = false;
}
Gfx::Painter painter(*window().backing_store());
painter.fill_rect(menubar_rect, palette.window());
painter.draw_line({ 0, menubar_rect.bottom() }, { menubar_rect.right(), menubar_rect.bottom() }, palette.threed_shadow1());
for_each_active_menubar_menu([&](Menu& menu) {
Color text_color = palette.window_text();
if (is_open(menu)) {
painter.fill_rect(menu.rect_in_menubar(), palette.menu_selection());
painter.draw_rect(menu.rect_in_menubar(), palette.menu_selection().darkened());
text_color = palette.menu_selection_text();
}
painter.draw_text(
menu.text_rect_in_menubar(),
menu.name(),
menu.title_font(),
Gfx::TextAlignment::CenterLeft,
text_color);
return IterationDecision::Continue;
});
AppletManager::the().draw();
}
void MenuManager::refresh()
{
if (!m_window)
return;
draw();
window().invalidate();
}
void MenuManager::event(Core::Event& event)
{
if (static_cast<Event&>(event).is_mouse_event()) {
handle_mouse_event(static_cast<MouseEvent&>(event));
return;
}
if (static_cast<Event&>(event).is_key_event()) {
auto& key_event = static_cast<const KeyEvent&>(event);
if (key_event.type() == Event::KeyUp && key_event.key() == Key_Escape) {
close_everyone();
return;
}
if (event.type() == Event::KeyDown) {
for_each_active_menubar_menu([&](Menu& menu) {
if (is_open(menu))
menu.dispatch_event(event);
return IterationDecision::Continue;
});
}
}
return Core::Object::event(event);
}
void MenuManager::handle_mouse_event(MouseEvent& mouse_event)
{
auto* active_window = WindowManager::the().active_window();
bool handled_menubar_event = false;
for_each_active_menubar_menu([&](Menu& menu) {
if (menu.rect_in_menubar().contains(mouse_event.position())) {
handled_menubar_event = &menu == m_system_menu || !active_window || !active_window->is_modal();
if (handled_menubar_event)
handle_menu_mouse_event(menu, mouse_event);
return IterationDecision::Break;
}
return IterationDecision::Continue;
});
if (handled_menubar_event)
return;
if (has_open_menu()) {
auto* topmost_menu = m_open_menu_stack.last().ptr();
ASSERT(topmost_menu);
auto* window = topmost_menu->menu_window();
if (!window) {
dbg() << "MenuManager::handle_mouse_event: No menu window";
return;
}
ASSERT(window->is_visible());
bool event_is_inside_current_menu = window->rect().contains(mouse_event.position());
if (event_is_inside_current_menu) {
WindowManager::the().set_hovered_window(window);
auto translated_event = mouse_event.translated(-window->position());
WindowManager::the().deliver_mouse_event(*window, translated_event);
return;
}
if (topmost_menu->hovered_item())
topmost_menu->clear_hovered_item();
if (mouse_event.type() == Event::MouseDown || mouse_event.type() == Event::MouseUp) {
auto* window_menu_of = topmost_menu->window_menu_of();
if (window_menu_of) {
bool event_is_inside_taskbar_button = window_menu_of->taskbar_rect().contains(mouse_event.position());
if (event_is_inside_taskbar_button && !topmost_menu->is_window_menu_open()) {
topmost_menu->set_window_menu_open(true);
return;
}
}
if (mouse_event.type() == Event::MouseDown) {
close_bar();
topmost_menu->set_window_menu_open(false);
}
}
if (mouse_event.type() == Event::MouseMove) {
for (auto& menu : m_open_menu_stack) {
if (!menu)
continue;
if (!menu->menu_window()->rect().contains(mouse_event.position()))
continue;
WindowManager::the().set_hovered_window(menu->menu_window());
auto translated_event = mouse_event.translated(-menu->menu_window()->position());
WindowManager::the().deliver_mouse_event(*menu->menu_window(), translated_event);
break;
}
}
return;
}
AppletManager::the().dispatch_event(static_cast<Event&>(mouse_event));
}
void MenuManager::handle_menu_mouse_event(Menu& menu, const MouseEvent& event)
{
bool is_hover_with_any_menu_open = event.type() == MouseEvent::MouseMove
&& has_open_menu()
&& (m_open_menu_stack.first()->menubar() || m_open_menu_stack.first() == m_system_menu.ptr());
bool is_mousedown_with_left_button = event.type() == MouseEvent::MouseDown && event.button() == MouseButton::Left;
bool should_open_menu = &menu != m_current_menu && (is_hover_with_any_menu_open || is_mousedown_with_left_button);
if (is_mousedown_with_left_button)
m_bar_open = !m_bar_open;
if (should_open_menu && m_bar_open) {
open_menu(menu);
return;
}
if (!m_bar_open)
close_everyone();
}
void MenuManager::set_needs_window_resize()
{
m_needs_window_resize = true;
}
void MenuManager::close_all_menus_from_client(Badge<ClientConnection>, ClientConnection& client)
{
if (!has_open_menu())
return;
if (m_open_menu_stack.first()->client() != &client)
return;
close_everyone();
}
void MenuManager::close_everyone()
{
for (auto& menu : m_open_menu_stack) {
if (menu && menu->menu_window())
menu->menu_window()->set_visible(false);
menu->clear_hovered_item();
}
m_open_menu_stack.clear();
m_current_menu = nullptr;
refresh();
}
void MenuManager::close_everyone_not_in_lineage(Menu& menu)
{
Vector<Menu*> menus_to_close;
for (auto& open_menu : m_open_menu_stack) {
if (!open_menu)
continue;
if (&menu == open_menu.ptr() || open_menu->is_menu_ancestor_of(menu))
continue;
menus_to_close.append(open_menu);
}
close_menus(menus_to_close);
}
void MenuManager::close_menus(const Vector<Menu*>& menus)
{
for (auto& menu : menus) {
if (menu == m_current_menu)
m_current_menu = nullptr;
if (menu->menu_window())
menu->menu_window()->set_visible(false);
menu->clear_hovered_item();
m_open_menu_stack.remove_first_matching([&](auto& entry) {
return entry == menu;
});
}
refresh();
}
static void collect_menu_subtree(Menu& menu, Vector<Menu*>& menus)
{
menus.append(&menu);
for (int i = 0; i < menu.item_count(); ++i) {
auto& item = menu.item(i);
if (!item.is_submenu())
continue;
collect_menu_subtree(*const_cast<MenuItem&>(item).submenu(), menus);
}
}
void MenuManager::close_menu_and_descendants(Menu& menu)
{
Vector<Menu*> menus_to_close;
collect_menu_subtree(menu, menus_to_close);
close_menus(menus_to_close);
}
void MenuManager::toggle_menu(Menu& menu)
{
if (is_open(menu)) {
close_menu_and_descendants(menu);
return;
}
open_menu(menu);
}
void MenuManager::open_menu(Menu& menu)
{
if (is_open(menu))
return;
if (!menu.is_empty()) {
menu.redraw_if_theme_changed();
auto& menu_window = menu.ensure_menu_window();
menu_window.move_to({ menu.rect_in_menubar().x(), menu.rect_in_menubar().bottom() + 2 });
menu_window.set_visible(true);
}
set_current_menu(&menu);
refresh();
}
void MenuManager::set_current_menu(Menu* menu, bool is_submenu)
{
if (menu == m_current_menu)
return;
if (!is_submenu) {
if (menu)
close_everyone_not_in_lineage(*menu);
else
close_everyone();
}
if (!menu) {
m_current_menu = nullptr;
return;
}
m_current_menu = menu->make_weak_ptr();
if (m_open_menu_stack.find([menu](auto& other) { return menu == other.ptr(); }).is_end())
m_open_menu_stack.append(menu->make_weak_ptr());
}
void MenuManager::close_bar()
{
close_everyone();
m_bar_open = false;
}
Gfx::Rect MenuManager::menubar_rect() const
{
return { 0, 0, Screen::the().rect().width(), 18 };
}
void MenuManager::set_current_menubar(MenuBar* menubar)
{
if (menubar)
m_current_menubar = menubar->make_weak_ptr();
else
m_current_menubar = nullptr;
#ifdef DEBUG_MENUS
dbg() << "[WM] Current menubar is now " << menubar;
#endif
Gfx::Point next_menu_location { MenuManager::menubar_menu_margin() / 2, 0 };
for_each_active_menubar_menu([&](Menu& menu) {
int text_width = menu.title_font().width(menu.name());
menu.set_rect_in_menubar({ next_menu_location.x() - MenuManager::menubar_menu_margin() / 2, 0, text_width + MenuManager::menubar_menu_margin(), menubar_rect().height() - 1 });
menu.set_text_rect_in_menubar({ next_menu_location, { text_width, menubar_rect().height() } });
next_menu_location.move_by(menu.rect_in_menubar().width(), 0);
return IterationDecision::Continue;
});
refresh();
}
void MenuManager::close_menubar(MenuBar& menubar)
{
if (current_menubar() == &menubar)
set_current_menubar(nullptr);
}
void MenuManager::set_system_menu(Menu& menu)
{
m_system_menu = menu.make_weak_ptr();
set_current_menubar(m_current_menubar);
}
void MenuManager::did_change_theme()
{
++m_theme_index;
refresh();
}
}

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include "Menu.h"
#include "MenuBar.h"
#include "Window.h"
#include <AK/HashMap.h>
#include <LibCore/Object.h>
namespace WindowServer {
class MenuManager final : public Core::Object {
C_OBJECT(MenuManager)
public:
static MenuManager& the();
MenuManager();
virtual ~MenuManager() override;
void refresh();
bool is_open(const Menu&) const;
bool has_open_menu() const { return !m_open_menu_stack.is_empty(); }
Gfx::Rect menubar_rect() const;
static int menubar_menu_margin() { return 16; }
void set_needs_window_resize();
Menu* current_menu() { return m_current_menu.ptr(); }
void set_current_menu(Menu*, bool is_submenu = false);
void open_menu(Menu&);
void toggle_menu(Menu&);
MenuBar* current_menubar() { return m_current_menubar.ptr(); }
void set_current_menubar(MenuBar*);
void close_menubar(MenuBar&);
void close_bar();
void close_everyone();
void close_everyone_not_in_lineage(Menu&);
void close_menu_and_descendants(Menu&);
void close_all_menus_from_client(Badge<ClientConnection>, ClientConnection&);
void toggle_system_menu()
{
if (m_system_menu)
toggle_menu(*m_system_menu);
}
Menu* system_menu() { return m_system_menu; }
void set_system_menu(Menu&);
int theme_index() const { return m_theme_index; }
Window& window() { return *m_window; }
template<typename Callback>
void for_each_active_menubar_menu(Callback callback)
{
if (system_menu()) {
if (callback(*system_menu()) == IterationDecision::Break)
return;
}
if (m_current_menubar)
m_current_menubar->for_each_menu(callback);
}
void did_change_theme();
private:
void close_menus(const Vector<Menu*>&);
const Window& window() const { return *m_window; }
virtual void event(Core::Event&) override;
void handle_mouse_event(MouseEvent&);
void handle_menu_mouse_event(Menu&, const MouseEvent&);
void draw();
RefPtr<Window> m_window;
WeakPtr<Menu> m_current_menu;
Vector<WeakPtr<Menu>> m_open_menu_stack;
WeakPtr<Menu> m_system_menu;
bool m_needs_window_resize { false };
bool m_bar_open { false };
int m_theme_index { 0 };
WeakPtr<MenuBar> m_current_menubar;
};
}

View file

@ -0,0 +1,171 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "Screen.h"
#include "Compositor.h"
#include "Event.h"
#include "EventLoop.h"
#include "WindowManager.h"
#include <Kernel/FB.h>
#include <Kernel/MousePacket.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
namespace WindowServer {
static Screen* s_the;
Screen& Screen::the()
{
ASSERT(s_the);
return *s_the;
}
Screen::Screen(unsigned desired_width, unsigned desired_height)
{
ASSERT(!s_the);
s_the = this;
m_framebuffer_fd = open("/dev/fb0", O_RDWR | O_CLOEXEC);
if (m_framebuffer_fd < 0) {
perror("failed to open /dev/fb0");
ASSERT_NOT_REACHED();
}
if (fb_set_buffer(m_framebuffer_fd, 0) == 0) {
m_can_set_buffer = true;
}
set_resolution(desired_width, desired_height);
m_cursor_location = rect().center();
}
Screen::~Screen()
{
}
bool Screen::set_resolution(int width, int height)
{
FBResolution resolution { 0, (unsigned)width, (unsigned)height };
int rc = fb_set_resolution(m_framebuffer_fd, &resolution);
#ifdef WSSCREEN_DEBUG
dbg() << "fb_set_resolution() - return code " << rc;
#endif
if (rc == 0) {
on_change_resolution(resolution.pitch, resolution.width, resolution.height);
return true;
}
if (rc == -1) {
dbg() << "Invalid resolution " << width << "x" << height;
on_change_resolution(resolution.pitch, resolution.width, resolution.height);
return false;
}
ASSERT_NOT_REACHED();
}
void Screen::on_change_resolution(int pitch, int width, int height)
{
if (m_framebuffer) {
size_t previous_size_in_bytes = m_size_in_bytes;
int rc = munmap(m_framebuffer, previous_size_in_bytes);
ASSERT(rc == 0);
}
int rc = fb_get_size_in_bytes(m_framebuffer_fd, &m_size_in_bytes);
ASSERT(rc == 0);
m_framebuffer = (Gfx::RGBA32*)mmap(nullptr, m_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, m_framebuffer_fd, 0);
ASSERT(m_framebuffer && m_framebuffer != (void*)-1);
m_pitch = pitch;
m_width = width;
m_height = height;
m_cursor_location.constrain(rect());
}
void Screen::set_buffer(int index)
{
ASSERT(m_can_set_buffer);
int rc = fb_set_buffer(m_framebuffer_fd, index);
ASSERT(rc == 0);
}
void Screen::on_receive_mouse_data(const MousePacket& packet)
{
auto prev_location = m_cursor_location;
if (packet.is_relative) {
m_cursor_location.move_by(packet.x, packet.y);
#ifdef WSSCREEN_DEBUG
dbgprintf("Screen: New Relative mouse point @ X %d, Y %d\n", m_cursor_location.x(), m_cursor_location.y());
#endif
} else {
m_cursor_location = { packet.x * m_width / 0xffff, packet.y * m_height / 0xffff };
#ifdef WSSCREEN_DEBUG
dbgprintf("Screen: New Absolute mouse point @ X %d, Y %d\n", m_cursor_location.x(), m_cursor_location.y());
#endif
}
m_cursor_location.constrain(rect());
unsigned buttons = packet.buttons;
unsigned prev_buttons = m_mouse_button_state;
m_mouse_button_state = buttons;
unsigned changed_buttons = prev_buttons ^ buttons;
auto post_mousedown_or_mouseup_if_needed = [&](MouseButton button) {
if (!(changed_buttons & (unsigned)button))
return;
auto message = make<MouseEvent>(buttons & (unsigned)button ? Event::MouseDown : Event::MouseUp, m_cursor_location, buttons, button, m_modifiers);
Core::EventLoop::current().post_event(WindowManager::the(), move(message));
};
post_mousedown_or_mouseup_if_needed(MouseButton::Left);
post_mousedown_or_mouseup_if_needed(MouseButton::Right);
post_mousedown_or_mouseup_if_needed(MouseButton::Middle);
post_mousedown_or_mouseup_if_needed(MouseButton::Back);
post_mousedown_or_mouseup_if_needed(MouseButton::Forward);
if (m_cursor_location != prev_location) {
auto message = make<MouseEvent>(Event::MouseMove, m_cursor_location, buttons, MouseButton::None, m_modifiers);
Core::EventLoop::current().post_event(WindowManager::the(), move(message));
}
if (packet.z) {
auto message = make<MouseEvent>(Event::MouseWheel, m_cursor_location, buttons, MouseButton::None, m_modifiers, packet.z);
Core::EventLoop::current().post_event(WindowManager::the(), move(message));
}
if (m_cursor_location != prev_location)
Compositor::the().invalidate_cursor();
}
void Screen::on_receive_keyboard_data(::KeyEvent kernel_event)
{
m_modifiers = kernel_event.modifiers();
auto message = make<KeyEvent>(kernel_event.is_press() ? Event::KeyDown : Event::KeyUp, kernel_event.key, kernel_event.character, kernel_event.modifiers());
Core::EventLoop::current().post_event(WindowManager::the(), move(message));
}
}

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <Kernel/KeyCode.h>
#include <LibGfx/Color.h>
#include <LibGfx/Rect.h>
#include <LibGfx/Size.h>
struct MousePacket;
namespace WindowServer {
class Screen {
public:
Screen(unsigned width, unsigned height);
~Screen();
bool set_resolution(int width, int height);
bool can_set_buffer() { return m_can_set_buffer; }
void set_buffer(int index);
int width() const { return m_width; }
int height() const { return m_height; }
size_t pitch() const { return m_pitch; }
Gfx::RGBA32* scanline(int y);
static Screen& the();
Gfx::Size size() const { return { width(), height() }; }
Gfx::Rect rect() const { return { 0, 0, width(), height() }; }
Gfx::Point cursor_location() const { return m_cursor_location; }
unsigned mouse_button_state() const { return m_mouse_button_state; }
void on_receive_mouse_data(const MousePacket&);
void on_receive_keyboard_data(::KeyEvent);
private:
void on_change_resolution(int pitch, int width, int height);
size_t m_size_in_bytes;
Gfx::RGBA32* m_framebuffer { nullptr };
bool m_can_set_buffer { false };
int m_pitch { 0 };
int m_width { 0 };
int m_height { 0 };
int m_framebuffer_fd { -1 };
Gfx::Point m_cursor_location;
unsigned m_mouse_button_state { 0 };
unsigned m_modifiers { 0 };
};
inline Gfx::RGBA32* Screen::scanline(int y)
{
return reinterpret_cast<Gfx::RGBA32*>(((u8*)m_framebuffer) + (y * m_pitch));
}
}

View file

@ -0,0 +1,524 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include "Window.h"
#include "ClientConnection.h"
#include "Event.h"
#include "EventLoop.h"
#include "Screen.h"
#include "WindowClientEndpoint.h"
#include "WindowManager.h"
#include <AK/Badge.h>
namespace WindowServer {
static String default_window_icon_path()
{
return "/res/icons/16x16/window.png";
}
static Gfx::Bitmap& default_window_icon()
{
static Gfx::Bitmap* s_icon;
if (!s_icon)
s_icon = Gfx::Bitmap::load_from_file(default_window_icon_path()).leak_ref();
return *s_icon;
}
static Gfx::Bitmap& minimize_icon()
{
static Gfx::Bitmap* s_icon;
if (!s_icon)
s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-minimize.png").leak_ref();
return *s_icon;
}
static Gfx::Bitmap& maximize_icon()
{
static Gfx::Bitmap* s_icon;
if (!s_icon)
s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-maximize.png").leak_ref();
return *s_icon;
}
static Gfx::Bitmap& restore_icon()
{
static Gfx::Bitmap* s_icon;
if (!s_icon)
s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-restore.png").leak_ref();
return *s_icon;
}
static Gfx::Bitmap& close_icon()
{
static Gfx::Bitmap* s_icon;
if (!s_icon)
s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-close.png").leak_ref();
return *s_icon;
}
Window::Window(Core::Object& parent, WindowType type)
: Core::Object(&parent)
, m_type(type)
, m_icon(default_window_icon())
, m_frame(*this)
{
WindowManager::the().add_window(*this);
}
Window::Window(ClientConnection& client, WindowType window_type, int window_id, bool modal, bool minimizable, bool frameless, bool resizable, bool fullscreen)
: Core::Object(&client)
, m_client(&client)
, m_type(window_type)
, m_modal(modal)
, m_minimizable(minimizable)
, m_frameless(frameless)
, m_resizable(resizable)
, m_fullscreen(fullscreen)
, m_window_id(window_id)
, m_client_id(client.client_id())
, m_icon(default_window_icon())
, m_frame(*this)
{
// FIXME: This should not be hard-coded here.
if (m_type == WindowType::Taskbar) {
m_wm_event_mask = WMEventMask::WindowStateChanges | WMEventMask::WindowRemovals | WMEventMask::WindowIconChanges;
m_listens_to_wm_events = true;
}
WindowManager::the().add_window(*this);
}
Window::~Window()
{
// Detach from client at the start of teardown since we don't want
// to confuse things by trying to send messages to it.
m_client = nullptr;
WindowManager::the().remove_window(*this);
}
void Window::set_title(const String& title)
{
if (m_title == title)
return;
m_title = title;
WindowManager::the().notify_title_changed(*this);
}
void Window::set_rect(const Gfx::Rect& rect)
{
ASSERT(!rect.is_empty());
Gfx::Rect old_rect;
if (m_rect == rect)
return;
old_rect = m_rect;
m_rect = rect;
if (!m_client && (!m_backing_store || old_rect.size() != rect.size())) {
m_backing_store = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, m_rect.size());
}
m_frame.notify_window_rect_changed(old_rect, rect);
}
void Window::set_rect_without_repaint(const Gfx::Rect& rect)
{
ASSERT(!rect.is_empty());
if (m_rect == rect)
return;
auto old_rect = m_rect;
m_rect = rect;
if (old_rect.size() == m_rect.size()) {
auto delta = m_rect.location() - old_rect.location();
for (auto& child_window : m_child_windows) {
if (child_window)
child_window->move_by(delta);
}
}
m_frame.notify_window_rect_changed(old_rect, rect);
}
void Window::handle_mouse_event(const MouseEvent& event)
{
set_automatic_cursor_tracking_enabled(event.buttons() != 0);
switch (event.type()) {
case Event::MouseMove:
m_client->post_message(Messages::WindowClient::MouseMove(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta(), event.is_drag(), event.drag_data_type()));
break;
case Event::MouseDown:
m_client->post_message(Messages::WindowClient::MouseDown(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
break;
case Event::MouseDoubleClick:
m_client->post_message(Messages::WindowClient::MouseDoubleClick(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
break;
case Event::MouseUp:
m_client->post_message(Messages::WindowClient::MouseUp(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
break;
case Event::MouseWheel:
m_client->post_message(Messages::WindowClient::MouseWheel(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
break;
default:
ASSERT_NOT_REACHED();
}
}
void Window::update_menu_item_text(PopupMenuItem item)
{
if (m_window_menu) {
m_window_menu->item((int)item).set_text(item == PopupMenuItem::Minimize ? (m_minimized ? "Unminimize" : "Minimize") : (m_maximized ? "Restore" : "Maximize"));
m_window_menu->redraw();
}
}
void Window::update_menu_item_enabled(PopupMenuItem item)
{
if (m_window_menu) {
m_window_menu->item((int)item).set_enabled(item == PopupMenuItem::Minimize ? m_minimizable : m_resizable);
m_window_menu->redraw();
}
}
void Window::set_minimized(bool minimized)
{
if (m_minimized == minimized)
return;
if (minimized && !m_minimizable)
return;
if (is_blocked_by_modal_window())
return;
m_minimized = minimized;
update_menu_item_text(PopupMenuItem::Minimize);
start_minimize_animation();
if (!minimized)
request_update({ {}, size() });
invalidate();
WindowManager::the().notify_minimization_state_changed(*this);
}
void Window::set_minimizable(bool minimizable)
{
if (m_minimizable == minimizable)
return;
m_minimizable = minimizable;
update_menu_item_enabled(PopupMenuItem::Minimize);
// TODO: Hide/show (or alternatively change enabled state of) window minimize button dynamically depending on value of m_minimizable
}
void Window::set_opacity(float opacity)
{
if (m_opacity == opacity)
return;
m_opacity = opacity;
WindowManager::the().notify_opacity_changed(*this);
}
void Window::set_occluded(bool occluded)
{
if (m_occluded == occluded)
return;
m_occluded = occluded;
WindowManager::the().notify_occlusion_state_changed(*this);
}
void Window::set_maximized(bool maximized)
{
if (m_maximized == maximized)
return;
if (maximized && !is_resizable())
return;
if (is_blocked_by_modal_window())
return;
set_tiled(WindowTileType::None);
m_maximized = maximized;
update_menu_item_text(PopupMenuItem::Maximize);
auto old_rect = m_rect;
if (maximized) {
m_unmaximized_rect = m_rect;
set_rect(WindowManager::the().maximized_window_rect(*this));
} else {
set_rect(m_unmaximized_rect);
}
m_frame.did_set_maximized({}, maximized);
Core::EventLoop::current().post_event(*this, make<ResizeEvent>(old_rect, m_rect));
}
void Window::set_resizable(bool resizable)
{
if (m_resizable == resizable)
return;
m_resizable = resizable;
update_menu_item_enabled(PopupMenuItem::Maximize);
// TODO: Hide/show (or alternatively change enabled state of) window maximize button dynamically depending on value of is_resizable()
}
void Window::event(Core::Event& event)
{
if (!m_client) {
ASSERT(parent());
event.ignore();
return;
}
if (is_blocked_by_modal_window())
return;
if (static_cast<Event&>(event).is_mouse_event())
return handle_mouse_event(static_cast<const MouseEvent&>(event));
switch (event.type()) {
case Event::WindowEntered:
m_client->post_message(Messages::WindowClient::WindowEntered(m_window_id));
break;
case Event::WindowLeft:
m_client->post_message(Messages::WindowClient::WindowLeft(m_window_id));
break;
case Event::KeyDown:
m_client->post_message(
Messages::WindowClient::KeyDown(m_window_id,
(u8) static_cast<const KeyEvent&>(event).character(),
(u32) static_cast<const KeyEvent&>(event).key(),
static_cast<const KeyEvent&>(event).modifiers()));
break;
case Event::KeyUp:
m_client->post_message(
Messages::WindowClient::KeyUp(m_window_id,
(u8) static_cast<const KeyEvent&>(event).character(),
(u32) static_cast<const KeyEvent&>(event).key(),
static_cast<const KeyEvent&>(event).modifiers()));
break;
case Event::WindowActivated:
m_client->post_message(Messages::WindowClient::WindowActivated(m_window_id));
break;
case Event::WindowDeactivated:
m_client->post_message(Messages::WindowClient::WindowDeactivated(m_window_id));
break;
case Event::WindowCloseRequest:
m_client->post_message(Messages::WindowClient::WindowCloseRequest(m_window_id));
break;
case Event::WindowResized:
m_client->post_message(
Messages::WindowClient::WindowResized(
m_window_id,
static_cast<const ResizeEvent&>(event).old_rect(),
static_cast<const ResizeEvent&>(event).rect()));
break;
default:
break;
}
}
void Window::set_global_cursor_tracking_enabled(bool enabled)
{
m_global_cursor_tracking_enabled = enabled;
}
void Window::set_visible(bool b)
{
if (m_visible == b)
return;
m_visible = b;
invalidate();
}
void Window::invalidate()
{
WindowManager::the().invalidate(*this);
}
void Window::invalidate(const Gfx::Rect& rect)
{
WindowManager::the().invalidate(*this, rect);
}
bool Window::is_active() const
{
return WindowManager::the().active_window() == this;
}
bool Window::is_blocked_by_modal_window() const
{
bool is_any_modal = false;
const Window* next = this;
while (!is_any_modal && next) {
is_any_modal = next->is_modal();
next = next->parent_window();
}
return !is_any_modal && client() && client()->is_showing_modal_window();
}
void Window::set_default_icon()
{
m_icon = default_window_icon();
}
void Window::request_update(const Gfx::Rect& rect, bool ignore_occlusion)
{
if (m_pending_paint_rects.is_empty()) {
deferred_invoke([this, ignore_occlusion](auto&) {
client()->post_paint_message(*this, ignore_occlusion);
});
}
m_pending_paint_rects.add(rect);
}
void Window::popup_window_menu(const Gfx::Point& position)
{
if (!m_window_menu) {
m_window_menu = Menu::construct(nullptr, -1, "(Window Menu)");
m_window_menu->set_window_menu_of(*this);
auto minimize_item = make<MenuItem>(*m_window_menu, 1, m_minimized ? "Unminimize" : "Minimize");
m_window_menu_minimize_item = minimize_item.ptr();
m_window_menu->add_item(move(minimize_item));
auto maximize_item = make<MenuItem>(*m_window_menu, 2, m_maximized ? "Restore" : "Maximize");
m_window_menu_maximize_item = maximize_item.ptr();
m_window_menu->add_item(move(maximize_item));
m_window_menu->add_item(make<MenuItem>(*m_window_menu, MenuItem::Type::Separator));
auto close_item = make<MenuItem>(*m_window_menu, 3, "Close");
close_item->set_icon(&close_icon());
m_window_menu->add_item(move(close_item));
m_window_menu->item((int)PopupMenuItem::Minimize).set_enabled(m_minimizable);
m_window_menu->item((int)PopupMenuItem::Maximize).set_enabled(m_resizable);
m_window_menu->on_item_activation = [&](auto& item) {
switch (item.identifier()) {
case 1:
set_minimized(!m_minimized);
if (!m_minimized)
WindowManager::the().move_to_front_and_make_active(*this);
break;
case 2:
set_maximized(!m_maximized);
if (m_minimized)
set_minimized(false);
WindowManager::the().move_to_front_and_make_active(*this);
break;
case 3:
request_close();
break;
}
};
}
m_window_menu_minimize_item->set_icon(m_minimized ? nullptr : &minimize_icon());
m_window_menu_maximize_item->set_icon(m_maximized ? &restore_icon() : &maximize_icon());
m_window_menu->popup(position);
}
void Window::request_close()
{
Event close_request(Event::WindowCloseRequest);
event(close_request);
}
void Window::set_fullscreen(bool fullscreen)
{
if (m_fullscreen == fullscreen)
return;
m_fullscreen = fullscreen;
Gfx::Rect new_window_rect = m_rect;
if (m_fullscreen) {
m_saved_nonfullscreen_rect = m_rect;
new_window_rect = Screen::the().rect();
} else if (!m_saved_nonfullscreen_rect.is_empty()) {
new_window_rect = m_saved_nonfullscreen_rect;
}
Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect, new_window_rect));
set_rect(new_window_rect);
}
Gfx::Rect Window::tiled_rect(WindowTileType tiled) const
{
int frame_width = (m_frame.rect().width() - m_rect.width()) / 2;
switch (tiled) {
case WindowTileType::None:
return m_untiled_rect;
case WindowTileType::Left:
return Gfx::Rect(0,
WindowManager::the().maximized_window_rect(*this).y(),
Screen::the().width() / 2 - frame_width,
WindowManager::the().maximized_window_rect(*this).height());
case WindowTileType::Right:
return Gfx::Rect(Screen::the().width() / 2 + frame_width,
WindowManager::the().maximized_window_rect(*this).y(),
Screen::the().width() / 2 - frame_width,
WindowManager::the().maximized_window_rect(*this).height());
default:
ASSERT_NOT_REACHED();
}
}
void Window::set_tiled(WindowTileType tiled)
{
if (m_tiled == tiled)
return;
m_tiled = tiled;
auto old_rect = m_rect;
if (tiled != WindowTileType::None)
m_untiled_rect = m_rect;
set_rect(tiled_rect(tiled));
Core::EventLoop::current().post_event(*this, make<ResizeEvent>(old_rect, m_rect));
}
void Window::detach_client(Badge<ClientConnection>)
{
m_client = nullptr;
}
void Window::recalculate_rect()
{
if (!is_resizable())
return;
auto old_rect = m_rect;
if (m_tiled != WindowTileType::None)
set_rect(tiled_rect(m_tiled));
else if (is_maximized())
set_rect(WindowManager::the().maximized_window_rect(*this));
Core::EventLoop::current().post_event(*this, make<ResizeEvent>(old_rect, m_rect));
}
void Window::add_child_window(Window& child_window)
{
m_child_windows.append(child_window.make_weak_ptr());
}
void Window::set_parent_window(Window& parent_window)
{
ASSERT(!m_parent_window);
m_parent_window = parent_window.make_weak_ptr();
parent_window.add_child_window(*this);
}
}

View file

@ -0,0 +1,286 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#pragma once
#include <AK/InlineLinkedList.h>
#include <AK/String.h>
#include <AK/WeakPtr.h>
#include <LibCore/Object.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/DisjointRectSet.h>
#include <LibGfx/Rect.h>
#include <WindowServer/WindowFrame.h>
#include <WindowServer/WindowType.h>
namespace WindowServer {
class ClientConnection;
class Cursor;
class Menu;
class MenuItem;
class MouseEvent;
enum WMEventMask {
WindowRectChanges = 1 << 0,
WindowStateChanges = 1 << 1,
WindowIconChanges = 1 << 2,
WindowRemovals = 1 << 3,
};
enum class WindowTileType {
None = 0,
Left,
Right,
};
enum class PopupMenuItem {
Minimize = 0,
Maximize,
};
class Window final : public Core::Object
, public InlineLinkedListNode<Window> {
C_OBJECT(Window)
public:
Window(ClientConnection&, WindowType, int window_id, bool modal, bool minimizable, bool frameless, bool resizable, bool fullscreen);
Window(Core::Object&, WindowType);
virtual ~Window() override;
void popup_window_menu(const Gfx::Point&);
void request_close();
unsigned wm_event_mask() const { return m_wm_event_mask; }
void set_wm_event_mask(unsigned mask) { m_wm_event_mask = mask; }
bool is_minimized() const { return m_minimized; }
void set_minimized(bool);
bool is_minimizable() const { return m_minimizable; }
void set_minimizable(bool);
bool is_resizable() const { return m_resizable && !m_fullscreen; }
void set_resizable(bool);
bool is_maximized() const { return m_maximized; }
void set_maximized(bool);
bool is_fullscreen() const { return m_fullscreen; }
void set_fullscreen(bool);
WindowTileType tiled() const { return m_tiled; }
void set_tiled(WindowTileType);
bool is_occluded() const { return m_occluded; }
void set_occluded(bool);
bool is_movable() const
{
return m_type == WindowType::Normal;
}
WindowFrame& frame() { return m_frame; }
const WindowFrame& frame() const { return m_frame; }
bool is_blocked_by_modal_window() const;
bool listens_to_wm_events() const { return m_listens_to_wm_events; }
ClientConnection* client() { return m_client; }
const ClientConnection* client() const { return m_client; }
WindowType type() const { return m_type; }
int window_id() const { return m_window_id; }
bool is_internal() const { return m_client_id == -1; }
i32 client_id() const { return m_client_id; }
String title() const { return m_title; }
void set_title(const String&);
float opacity() const { return m_opacity; }
void set_opacity(float);
int x() const { return m_rect.x(); }
int y() const { return m_rect.y(); }
int width() const { return m_rect.width(); }
int height() const { return m_rect.height(); }
bool is_active() const;
bool is_visible() const { return m_visible; }
void set_visible(bool);
bool is_modal() const { return m_modal; }
Gfx::Rect rect() const { return m_rect; }
void set_rect(const Gfx::Rect&);
void set_rect(int x, int y, int width, int height) { set_rect({ x, y, width, height }); }
void set_rect_without_repaint(const Gfx::Rect&);
void set_taskbar_rect(const Gfx::Rect& rect) { m_taskbar_rect = rect; }
const Gfx::Rect& taskbar_rect() const { return m_taskbar_rect; }
void move_to(const Gfx::Point& position) { set_rect({ position, size() }); }
void move_to(int x, int y) { move_to({ x, y }); }
void move_by(const Gfx::Point& delta) { set_position_without_repaint(position().translated(delta)); }
Gfx::Point position() const { return m_rect.location(); }
void set_position(const Gfx::Point& position) { set_rect({ position.x(), position.y(), width(), height() }); }
void set_position_without_repaint(const Gfx::Point& position) { set_rect_without_repaint({ position.x(), position.y(), width(), height() }); }
Gfx::Size size() const { return m_rect.size(); }
void invalidate();
void invalidate(const Gfx::Rect&);
virtual void event(Core::Event&) override;
// Only used by WindowType::MenuApplet. Perhaps it could be a Window subclass? I don't know.
void set_rect_in_menubar(const Gfx::Rect& rect) { m_rect_in_menubar = rect; }
const Gfx::Rect& rect_in_menubar() const { return m_rect_in_menubar; }
const Gfx::Bitmap* backing_store() const { return m_backing_store.ptr(); }
Gfx::Bitmap* backing_store() { return m_backing_store.ptr(); }
void set_backing_store(RefPtr<Gfx::Bitmap>&& backing_store)
{
m_last_backing_store = move(m_backing_store);
m_backing_store = move(backing_store);
}
void swap_backing_stores()
{
swap(m_backing_store, m_last_backing_store);
}
Gfx::Bitmap* last_backing_store() { return m_last_backing_store.ptr(); }
void set_global_cursor_tracking_enabled(bool);
void set_automatic_cursor_tracking_enabled(bool enabled) { m_automatic_cursor_tracking_enabled = enabled; }
bool global_cursor_tracking() const { return m_global_cursor_tracking_enabled || m_automatic_cursor_tracking_enabled; }
bool has_alpha_channel() const { return m_has_alpha_channel; }
void set_has_alpha_channel(bool value) { m_has_alpha_channel = value; }
Gfx::Size size_increment() const { return m_size_increment; }
void set_size_increment(const Gfx::Size& increment) { m_size_increment = increment; }
Gfx::Size base_size() const { return m_base_size; }
void set_base_size(const Gfx::Size& size) { m_base_size = size; }
const Gfx::Bitmap& icon() const { return *m_icon; }
void set_icon(NonnullRefPtr<Gfx::Bitmap>&& icon) { m_icon = move(icon); }
void set_default_icon();
const Cursor* override_cursor() const { return m_override_cursor.ptr(); }
void set_override_cursor(RefPtr<Cursor>&& cursor) { m_override_cursor = move(cursor); }
void request_update(const Gfx::Rect&, bool ignore_occlusion = false);
Gfx::DisjointRectSet take_pending_paint_rects() { return move(m_pending_paint_rects); }
bool in_minimize_animation() const { return m_minimize_animation_step != -1; }
int minimize_animation_index() const { return m_minimize_animation_step; }
void step_minimize_animation() { m_minimize_animation_step += 1; }
void start_minimize_animation() { m_minimize_animation_step = 0; }
void end_minimize_animation() { m_minimize_animation_step = -1; }
Gfx::Rect tiled_rect(WindowTileType) const;
void recalculate_rect();
// For InlineLinkedList.
// FIXME: Maybe make a ListHashSet and then WindowManager can just use that.
Window* m_next { nullptr };
Window* m_prev { nullptr };
void detach_client(Badge<ClientConnection>);
Window* parent_window() { return m_parent_window; }
const Window* parent_window() const { return m_parent_window; }
void set_parent_window(Window&);
Vector<WeakPtr<Window>>& child_windows() { return m_child_windows; }
const Vector<WeakPtr<Window>>& child_windows() const { return m_child_windows; }
void set_frameless(bool frameless) { m_frameless = frameless; }
bool is_frameless() const { return m_frameless; }
private:
void handle_mouse_event(const MouseEvent&);
void update_menu_item_text(PopupMenuItem item);
void update_menu_item_enabled(PopupMenuItem item);
void add_child_window(Window&);
ClientConnection* m_client { nullptr };
WeakPtr<Window> m_parent_window;
Vector<WeakPtr<Window>> m_child_windows;
String m_title;
Gfx::Rect m_rect;
Gfx::Rect m_saved_nonfullscreen_rect;
Gfx::Rect m_taskbar_rect;
WindowType m_type { WindowType::Normal };
bool m_global_cursor_tracking_enabled { false };
bool m_automatic_cursor_tracking_enabled { false };
bool m_visible { true };
bool m_has_alpha_channel { false };
bool m_modal { false };
bool m_minimizable { false };
bool m_frameless { false };
bool m_resizable { false };
bool m_listens_to_wm_events { false };
bool m_minimized { false };
bool m_maximized { false };
bool m_fullscreen { false };
WindowTileType m_tiled { WindowTileType::None };
Gfx::Rect m_untiled_rect;
bool m_occluded { false };
RefPtr<Gfx::Bitmap> m_backing_store;
RefPtr<Gfx::Bitmap> m_last_backing_store;
int m_window_id { -1 };
i32 m_client_id { -1 };
float m_opacity { 1 };
Gfx::Size m_size_increment;
Gfx::Size m_base_size;
NonnullRefPtr<Gfx::Bitmap> m_icon;
RefPtr<Cursor> m_override_cursor;
WindowFrame m_frame;
unsigned m_wm_event_mask { 0 };
Gfx::DisjointRectSet m_pending_paint_rects;
Gfx::Rect m_unmaximized_rect;
Gfx::Rect m_rect_in_menubar;
RefPtr<Menu> m_window_menu;
MenuItem* m_window_menu_minimize_item { nullptr };
MenuItem* m_window_menu_maximize_item { nullptr };
int m_minimize_animation_step { -1 };
};
}

View file

@ -0,0 +1,40 @@
endpoint WindowClient = 4
{
Paint(i32 window_id, Gfx::Size window_size, Vector<Gfx::Rect> rects) =|
MouseMove(i32 window_id, Gfx::Point mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta, bool is_drag, String drag_data_type) =|
MouseDown(i32 window_id, Gfx::Point mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta) =|
MouseDoubleClick(i32 window_id, Gfx::Point mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta) =|
MouseUp(i32 window_id, Gfx::Point mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta) =|
MouseWheel(i32 window_id, Gfx::Point mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta) =|
WindowEntered(i32 window_id) =|
WindowLeft(i32 window_id) =|
KeyDown(i32 window_id, u8 character, u32 key, u32 modifiers) =|
KeyUp(i32 window_id, u8 character, u32 key, u32 modifiers) =|
WindowActivated(i32 window_id) =|
WindowDeactivated(i32 window_id) =|
WindowStateChanged(i32 window_id, bool minimized, bool occluded) =|
WindowCloseRequest(i32 window_id) =|
WindowResized(i32 window_id, Gfx::Rect old_rect, Gfx::Rect new_rect) =|
MenuItemActivated(i32 menu_id, i32 identifier) =|
ScreenRectChanged(Gfx::Rect rect) =|
ClipboardContentsChanged(String content_type) =|
WM_WindowRemoved(i32 wm_id, i32 client_id, i32 window_id) =|
WM_WindowStateChanged(i32 wm_id, i32 client_id, i32 window_id, bool is_active, bool is_minimized, bool is_frameless, i32 window_type, String title, Gfx::Rect rect) =|
WM_WindowIconBitmapChanged(i32 wm_id, i32 client_id, i32 window_id, i32 icon_buffer_id, Gfx::Size icon_size) =|
WM_WindowRectChanged(i32 wm_id, i32 client_id, i32 window_id, Gfx::Rect rect) =|
AsyncSetWallpaperFinished(bool success) =|
DragAccepted() =|
DragCancelled() =|
DragDropped(i32 window_id, Gfx::Point mouse_position, String text, String data_type, String data) =|
UpdateSystemTheme(i32 system_theme_buffer_id) =|
DisplayLinkNotification() =|
}

View file

@ -0,0 +1,414 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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.
*/
#include <AK/Badge.h>
#include <LibGfx/CharacterBitmap.h>
#include <LibGfx/Font.h>
#include <LibGfx/Painter.h>
#include <LibGfx/StylePainter.h>
#include <WindowServer/Button.h>
#include <WindowServer/Compositor.h>
#include <WindowServer/Event.h>
#include <WindowServer/Window.h>
#include <WindowServer/WindowFrame.h>
#include <WindowServer/WindowManager.h>
namespace WindowServer {
static const int window_titlebar_height = 19;
static const char* s_close_button_bitmap_data = {
"## ##"
"### ###"
" ###### "
" #### "
" #### "
" ###### "
"### ###"
"## ##"
" "
};
static Gfx::CharacterBitmap* s_close_button_bitmap;
static const int s_close_button_bitmap_width = 8;
static const int s_close_button_bitmap_height = 9;
static const char* s_minimize_button_bitmap_data = {
" "
" "
" "
" ###### "
" #### "
" ## "
" "
" "
" "
};
static Gfx::CharacterBitmap* s_minimize_button_bitmap;
static const int s_minimize_button_bitmap_width = 8;
static const int s_minimize_button_bitmap_height = 9;
static const char* s_maximize_button_bitmap_data = {
" "
" "
" "
" ## "
" #### "
" ###### "
" "
" "
" "
};
static Gfx::CharacterBitmap* s_maximize_button_bitmap;
static const int s_maximize_button_bitmap_width = 8;
static const int s_maximize_button_bitmap_height = 9;
static const char* s_unmaximize_button_bitmap_data = {
" "
" ## "
" #### "
" ###### "
" "
" ###### "
" #### "
" ## "
" "
};
static Gfx::CharacterBitmap* s_unmaximize_button_bitmap;
static const int s_unmaximize_button_bitmap_width = 8;
static const int s_unmaximize_button_bitmap_height = 9;
WindowFrame::WindowFrame(Window& window)
: m_window(window)
{
if (!s_close_button_bitmap)
s_close_button_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_close_button_bitmap_data, s_close_button_bitmap_width, s_close_button_bitmap_height).leak_ref();
if (!s_minimize_button_bitmap)
s_minimize_button_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_minimize_button_bitmap_data, s_minimize_button_bitmap_width, s_minimize_button_bitmap_height).leak_ref();
if (!s_maximize_button_bitmap)
s_maximize_button_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_maximize_button_bitmap_data, s_maximize_button_bitmap_width, s_maximize_button_bitmap_height).leak_ref();
if (!s_unmaximize_button_bitmap)
s_unmaximize_button_bitmap = &Gfx::CharacterBitmap::create_from_ascii(s_unmaximize_button_bitmap_data, s_unmaximize_button_bitmap_width, s_unmaximize_button_bitmap_height).leak_ref();
m_buttons.append(make<Button>(*this, *s_close_button_bitmap, [this](auto&) {
m_window.request_close();
}));
if (window.is_resizable()) {
auto button = make<Button>(*this, *s_maximize_button_bitmap, [this](auto&) {
m_window.set_maximized(!m_window.is_maximized());
});
m_maximize_button = button.ptr();
m_buttons.append(move(button));
}
if (window.is_minimizable()) {
auto button = make<Button>(*this, *s_minimize_button_bitmap, [this](auto&) {
m_window.set_minimized(true);
});
m_minimize_button = button.ptr();
m_buttons.append(move(button));
}
}
WindowFrame::~WindowFrame()
{
}
void WindowFrame::did_set_maximized(Badge<Window>, bool maximized)
{
ASSERT(m_maximize_button);
m_maximize_button->set_bitmap(maximized ? *s_unmaximize_button_bitmap : *s_maximize_button_bitmap);
}
Gfx::Rect WindowFrame::title_bar_rect() const
{
if (m_window.type() == WindowType::Notification)
return { m_window.width() + 3, 3, window_titlebar_height, m_window.height() };
return { 4, 4, m_window.width(), window_titlebar_height };
}
Gfx::Rect WindowFrame::title_bar_icon_rect() const
{
auto titlebar_rect = title_bar_rect();
return {
titlebar_rect.x() + 1,
titlebar_rect.y() + 2,
16,
titlebar_rect.height(),
};
}
Gfx::Rect WindowFrame::title_bar_text_rect() const
{
auto titlebar_rect = title_bar_rect();
auto titlebar_icon_rect = title_bar_icon_rect();
return {
titlebar_rect.x() + 2 + titlebar_icon_rect.width() + 2,
titlebar_rect.y(),
titlebar_rect.width() - 4 - titlebar_icon_rect.width() - 2,
titlebar_rect.height()
};
}
WindowFrame::FrameColors WindowFrame::compute_frame_colors() const
{
auto& wm = WindowManager::the();
auto palette = wm.palette();
if (&m_window == wm.m_highlight_window)
return { palette.highlight_window_title(), palette.highlight_window_border1(), palette.highlight_window_border2() };
if (&m_window == wm.m_move_window)
return { palette.moving_window_title(), palette.moving_window_border1(), palette.moving_window_border2() };
if (&m_window == wm.m_active_window)
return { palette.active_window_title(), palette.active_window_border1(), palette.active_window_border2() };
return { palette.inactive_window_title(), palette.inactive_window_border1(), palette.inactive_window_border2() };
}
void WindowFrame::paint_notification_frame(Gfx::Painter& painter)
{
auto palette = WindowManager::the().palette();
Gfx::Rect outer_rect = { {}, rect().size() };
Gfx::StylePainter::paint_window_frame(painter, outer_rect, palette);
auto titlebar_rect = title_bar_rect();
painter.fill_rect_with_gradient(Gfx::Orientation::Vertical, titlebar_rect, palette.active_window_border1(), palette.active_window_border2());
int stripe_top = m_buttons.last().relative_rect().bottom() + 4;
int stripe_bottom = m_window.height() - 3;
if (stripe_top && stripe_bottom && stripe_top < stripe_bottom) {
for (int i = 2; i <= window_titlebar_height - 2; i += 2) {
painter.draw_line({ titlebar_rect.x() + i, stripe_top }, { titlebar_rect.x() + i, stripe_bottom }, palette.active_window_border1());
}
}
}
void WindowFrame::paint_normal_frame(Gfx::Painter& painter)
{
auto palette = WindowManager::the().palette();
auto& window = m_window;
Gfx::Rect outer_rect = { {}, rect().size() };
Gfx::StylePainter::paint_window_frame(painter, outer_rect, palette);
auto titlebar_rect = title_bar_rect();
auto titlebar_icon_rect = title_bar_icon_rect();
auto titlebar_inner_rect = title_bar_text_rect();
auto titlebar_title_rect = titlebar_inner_rect;
titlebar_title_rect.set_width(Gfx::Font::default_bold_font().width(window.title()));
auto [title_color, border_color, border_color2] = compute_frame_colors();
auto& wm = WindowManager::the();
painter.draw_line(titlebar_rect.bottom_left().translated(0, 1), titlebar_rect.bottom_right().translated(0, 1), palette.button());
painter.draw_line(titlebar_rect.bottom_left().translated(0, 2), titlebar_rect.bottom_right().translated(0, 2), palette.button());
auto leftmost_button_rect = m_buttons.is_empty() ? Gfx::Rect() : m_buttons.last().relative_rect();
painter.fill_rect_with_gradient(titlebar_rect, border_color, border_color2);
int stripe_left = titlebar_title_rect.right() + 4;
int stripe_right = leftmost_button_rect.left() - 3;
if (stripe_left && stripe_right && stripe_left < stripe_right) {
for (int i = 2; i <= titlebar_inner_rect.height() - 2; i += 2) {
painter.draw_line({ stripe_left, titlebar_inner_rect.y() + i }, { stripe_right, titlebar_inner_rect.y() + i }, border_color);
}
}
auto clipped_title_rect = titlebar_title_rect;
clipped_title_rect.set_width(stripe_right - clipped_title_rect.x());
if (!clipped_title_rect.is_empty()) {
painter.draw_text(clipped_title_rect.translated(1, 2), window.title(), wm.window_title_font(), Gfx::TextAlignment::CenterLeft, border_color.darkened(0.4), Gfx::TextElision::Right);
// FIXME: The translated(0, 1) wouldn't be necessary if we could center text based on its baseline.
painter.draw_text(clipped_title_rect.translated(0, 1), window.title(), wm.window_title_font(), Gfx::TextAlignment::CenterLeft, title_color, Gfx::TextElision::Right);
}
painter.blit(titlebar_icon_rect.location(), window.icon(), window.icon().rect());
}
void WindowFrame::paint(Gfx::Painter& painter)
{
if (m_window.is_frameless())
return;
Gfx::PainterStateSaver saver(painter);
painter.translate(rect().location());
if (m_window.type() == WindowType::Notification)
paint_notification_frame(painter);
else if (m_window.type() == WindowType::Normal)
paint_normal_frame(painter);
else
return;
for (auto& button : m_buttons) {
button.paint(painter);
}
}
static Gfx::Rect frame_rect_for_window(Window& window, const Gfx::Rect& rect)
{
if (window.is_frameless())
return rect;
auto type = window.type();
switch (type) {
case WindowType::Normal:
return {
rect.x() - 4,
rect.y() - window_titlebar_height - 6,
rect.width() + 8,
rect.height() + 10 + window_titlebar_height
};
case WindowType::Notification:
return {
rect.x() - 3,
rect.y() - 3,
rect.width() + 6 + window_titlebar_height,
rect.height() + 6
};
default:
return rect;
}
}
static Gfx::Rect frame_rect_for_window(Window& window)
{
return frame_rect_for_window(window, window.rect());
}
Gfx::Rect WindowFrame::rect() const
{
return frame_rect_for_window(m_window);
}
void WindowFrame::invalidate_title_bar()
{
WindowManager::the().invalidate(title_bar_rect().translated(rect().location()));
}
void WindowFrame::notify_window_rect_changed(const Gfx::Rect& old_rect, const Gfx::Rect& new_rect)
{
int window_button_width = 15;
int window_button_height = 15;
int pos;
if (m_window.type() == WindowType::Notification)
pos = title_bar_rect().top() + 2;
else
pos = title_bar_text_rect().right() + 1;
for (auto& button : m_buttons) {
if (m_window.type() == WindowType::Notification) {
Gfx::Rect rect { 0, pos, window_button_width, window_button_height };
rect.center_horizontally_within(title_bar_rect());
button.set_relative_rect(rect);
pos += window_button_width;
} else {
pos -= window_button_width;
Gfx::Rect rect { pos, 0, window_button_width, window_button_height };
rect.center_vertically_within(title_bar_text_rect());
button.set_relative_rect(rect);
}
}
auto& wm = WindowManager::the();
wm.invalidate(frame_rect_for_window(m_window, old_rect));
wm.invalidate(frame_rect_for_window(m_window, new_rect));
wm.notify_rect_changed(m_window, old_rect, new_rect);
}
void WindowFrame::on_mouse_event(const MouseEvent& event)
{
ASSERT(!m_window.is_fullscreen());
if (m_window.is_blocked_by_modal_window())
return;
auto& wm = WindowManager::the();
if (m_window.type() != WindowType::Normal && m_window.type() != WindowType::Notification)
return;
if (m_window.type() == WindowType::Normal && event.type() == Event::MouseDown && (event.button() == MouseButton::Left || event.button() == MouseButton::Right) && title_bar_icon_rect().contains(event.position())) {
wm.move_to_front_and_make_active(m_window);
m_window.popup_window_menu(title_bar_rect().bottom_left().translated(rect().location()));
return;
}
// This is slightly hackish, but expand the title bar rect by two pixels downwards,
// so that mouse events between the title bar and window contents don't act like
// mouse events on the border.
auto adjusted_title_bar_rect = title_bar_rect();
adjusted_title_bar_rect.set_height(adjusted_title_bar_rect.height() + 2);
if (adjusted_title_bar_rect.contains(event.position())) {
wm.clear_resize_candidate();
if (event.type() == Event::MouseDown)
wm.move_to_front_and_make_active(m_window);
for (auto& button : m_buttons) {
if (button.relative_rect().contains(event.position()))
return button.on_mouse_event(event.translated(-button.relative_rect().location()));
}
if (event.type() == Event::MouseDown) {
if (m_window.type() == WindowType::Normal && event.button() == MouseButton::Right) {
m_window.popup_window_menu(event.position().translated(rect().location()));
return;
}
if (m_window.is_movable() && event.button() == MouseButton::Left)
wm.start_window_move(m_window, event.translated(rect().location()));
}
return;
}
if (m_window.is_resizable() && event.type() == Event::MouseMove && event.buttons() == 0) {
constexpr ResizeDirection direction_for_hot_area[3][3] = {
{ ResizeDirection::UpLeft, ResizeDirection::Up, ResizeDirection::UpRight },
{ ResizeDirection::Left, ResizeDirection::None, ResizeDirection::Right },
{ ResizeDirection::DownLeft, ResizeDirection::Down, ResizeDirection::DownRight },
};
Gfx::Rect outer_rect = { {}, rect().size() };
ASSERT(outer_rect.contains(event.position()));
int window_relative_x = event.x() - outer_rect.x();
int window_relative_y = event.y() - outer_rect.y();
int hot_area_row = min(2, window_relative_y / (outer_rect.height() / 3));
int hot_area_column = min(2, window_relative_x / (outer_rect.width() / 3));
wm.set_resize_candidate(m_window, direction_for_hot_area[hot_area_row][hot_area_column]);
Compositor::the().invalidate_cursor();
return;
}
if (m_window.is_resizable() && event.type() == Event::MouseDown && event.button() == MouseButton::Left)
wm.start_window_resize(m_window, event.translated(rect().location()));
}
}

Some files were not shown because too many files have changed in this diff Show more