mirror of
https://github.com/FelixKratz/SketchyBar
synced 2024-11-10 05:44:16 +00:00
first commit
This commit is contained in:
commit
7724fb905a
46 changed files with 4576 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/bin
|
||||
/archive
|
||||
/.idea
|
4
.travis.yml
Normal file
4
.travis.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
language: c
|
||||
os: osx
|
||||
compiler: clang
|
||||
script: make install
|
9
CHANGELOG.md
Normal file
9
CHANGELOG.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
[Unreleased]: https://github.com/somdoron/spacebar/compare/master...HEAD
|
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 Åsmund Vikane
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
67
README.md
Normal file
67
README.md
Normal file
|
@ -0,0 +1,67 @@
|
|||
<!-- Please be careful editing the below HTML, as GitHub is quite finicky with anything that looks like an HTML tag in GitHub Flavored Markdown. -->
|
||||
<p align="center">
|
||||
<b>Status Bar for the Mac.</b>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.org/somdoron/spacebar">
|
||||
<img src="https://travis-ci.org/somdoron/spacebar.svg?branch=master" alt="CI Status Badge">
|
||||
</a>
|
||||
<a href="https://github.com/somdoron/spacebar/blob/master/LICENSE.txt">
|
||||
<img src="https://img.shields.io/github/license/somdoron/spacebar.svg?color=green" alt="License Badge">
|
||||
</a>
|
||||
<a href="https://github.com/spacebar/blob/blob/master/CHANGELOG.md">
|
||||
<img src="https://img.shields.io/badge/view-changelog-green.svg" alt="Changelog Badge">
|
||||
</a>
|
||||
<a href="https://github.com/somdoron/spacebar/releases">
|
||||
<img src="https://img.shields.io/github/commits-since/somdoron/spacebar/latest.svg?color=green" alt="Version Badge">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## About
|
||||
|
||||
spacebar is a status bar for [↗ yabai][gh-yabai] tiling window management.
|
||||
|
||||
## Installation and Configuration
|
||||
|
||||
- TODO: release package
|
||||
|
||||
- Sample configuration files can be found in the [↗ examples][spacebar-examples] directory. Refer to the [↗ documentation][spacebar-docs].
|
||||
|
||||
|
||||
## Requirements and Caveats
|
||||
|
||||
Please read the below requirements carefully.
|
||||
Make sure you fulfil all of them before filing an issue.
|
||||
|
||||
|Requirement|Note|
|
||||
|-:|:-|
|
||||
|Operating System|macOS Catalina 10.15.0+ is supported.|
|
||||
|Accessibility API|spacebar must be given permission to utilize the Accessibility API and will request access upon launch. The application must be restarted after access has been granted.|
|
||||
|
||||
Please also take note of the following caveats.
|
||||
|
||||
|Caveat|Note|
|
||||
|-:|:-|
|
||||
|Code Signing|When building from source (or installing from HEAD), it is recommended to codesign the binary so it retains its accessibility and automation privileges when updated or rebuilt.|
|
||||
|Mission Control|In the Mission Control preferences pane in System Preferences, the setting "Automatically rearrange Spaces based on most recent use" should be disabled.|
|
||||
|
||||
## License and Attribution
|
||||
|
||||
spacebar is licensed under the [↗ MIT License][spacebar-license], a short and simple permissive license with conditions only requiring preservation of copyright and license notices.
|
||||
Licensed works, modifications, and larger works may be distributed under different terms and without source code.
|
||||
|
||||
Thanks to [@koekeishiya][gh-koekeishiya] for creating yabai.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Use at your own discretion.
|
||||
I take no responsibility if anything should happen to your machine while trying to install, test or otherwise use this software in any form.
|
||||
|
||||
<!-- Project internal links -->
|
||||
[spacebar-license]: LICENSE.txt
|
||||
[spacebar-examples]: https://github.com/somdoron/spacebar/tree/master/examples
|
||||
[spacebar-docs]: https://github.com/somdoron/spacebar/blob/master/doc/spacebar.asciidoc
|
||||
|
||||
<!-- Links to other GitHub projects/users -->
|
||||
[gh-koekeishiya]: https://github.com/koekeishiya
|
||||
[gh-yabai]: https://github.com/koekeishiya/yabai
|
123
doc/spacebar.1
Normal file
123
doc/spacebar.1
Normal file
|
@ -0,0 +1,123 @@
|
|||
'\" t
|
||||
.\" Title: spacebar
|
||||
.\" Author: [see the "AUTHOR(S)" section]
|
||||
.\" Generator: Asciidoctor 2.0.10
|
||||
.\" Date: 2020-03-29
|
||||
.\" Manual: Spacebar Manual
|
||||
.\" Source: Spacebar
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "SPACEBAR" "1" "2020-03-29" "Spacebar" "Spacebar Manual"
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.ss \n[.ss] 0
|
||||
.nh
|
||||
.ad l
|
||||
.de URL
|
||||
\fI\\$2\fP <\\$1>\\$3
|
||||
..
|
||||
.als MTO URL
|
||||
.if \n[.g] \{\
|
||||
. mso www.tmac
|
||||
. am URL
|
||||
. ad l
|
||||
. .
|
||||
. am MTO
|
||||
. ad l
|
||||
. .
|
||||
. LINKSTYLE blue R < >
|
||||
.\}
|
||||
.SH "NAME"
|
||||
spacebar \- status bar for yabai tile window management utility
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBspacebar\fP [\fB\-v\fP,\fB\-\-version\fP|\fB\-V\fP,\fB\-\-verbose\fP|\fB\-m\fP,\fB\-\-message\fP \fImsg\fP|\fB\-c\fP,\fB\-\-config\fP \fIconfig_file\fP]
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
\fBspacebar\fP is a tiling window manager for macOS based on binary space partitioning.
|
||||
.SH "OPTIONS"
|
||||
.sp
|
||||
\fB\-v\fP, \fB\-\-version\fP
|
||||
.RS 4
|
||||
Print the version and exit.
|
||||
.RE
|
||||
.sp
|
||||
\fB\-V\fP, \fB\-\-verbose\fP
|
||||
.RS 4
|
||||
Output debug information to stdout.
|
||||
.RE
|
||||
.sp
|
||||
\fB\-m\fP, \fB\-\-message\fP \fI<msg>\fP
|
||||
.RS 4
|
||||
Send message to a running instance of spacebar.
|
||||
.RE
|
||||
.sp
|
||||
\fB\-c\fP, \fB\-\-config\fP \fI<config_file>\fP
|
||||
.RS 4
|
||||
Use the specified configuration file.
|
||||
.RE
|
||||
.SH "CONFIG"
|
||||
.SS "General Syntax"
|
||||
.sp
|
||||
spacebar \-m config <setting>
|
||||
.RS 4
|
||||
Get or set the value of <setting>.
|
||||
.RE
|
||||
.SS "Settings"
|
||||
.sp
|
||||
\fBdebug_output\fP [\fI<BOOL_SEL>\fP]
|
||||
.RS 4
|
||||
Enable output of debug information to stdout.
|
||||
.RE
|
||||
.sp
|
||||
\fBstatus_bar_text_font\fP [\fI<font_family>:<font_style>:<font_size>\fP]
|
||||
.RS 4
|
||||
Specify name, style and size of font to use for drawing text.
|
||||
.br
|
||||
Use \fIFont Book.app\fP to identify the correct name.
|
||||
.RE
|
||||
.sp
|
||||
\fBstatus_bar_icon_font\fP [\fI<font_family>:<font_style>:<font_size>\fP]
|
||||
.RS 4
|
||||
Specify name, style and size of font to use for drawing icon symbols.
|
||||
.br
|
||||
Use \fIFont Book.app\fP to identify the correct name.
|
||||
.RE
|
||||
.sp
|
||||
\fBstatus_bar_background_color\fP [\fI<COLOR>\fP]
|
||||
.RS 4
|
||||
Color to use for drawing status bar background.
|
||||
.RE
|
||||
.sp
|
||||
\fBstatus_bar_foreground_color\fP [\fI<COLOR>\fP]
|
||||
.RS 4
|
||||
Color to use for drawing status bar elements.
|
||||
.RE
|
||||
.sp
|
||||
\fBstatus_bar_space_icon_strip\fP [\fI<sym_1> <sym_2> <sym_n>\fP]
|
||||
.RS 4
|
||||
Specify symbols separated by whitespace to be used for visualizing spaces.
|
||||
.RE
|
||||
.sp
|
||||
\fBstatus_bar_power_icon_strip\fP [\fI<sym_battery> <sym_ac>\fP]
|
||||
.RS 4
|
||||
Specify two symbols separated by whitespace.
|
||||
.br
|
||||
The first symbol represents battery power and the second symbol indicates AC.
|
||||
.RE
|
||||
.sp
|
||||
\fBstatus_bar_space_icon\fP [\fI<sym>\fP]
|
||||
.RS 4
|
||||
Specify a general symbol to use for any given space that does not have a match in \fIstatus_bar_space_icon_strip\fP.
|
||||
.RE
|
||||
.sp
|
||||
\fBstatus_bar_clock_icon\fP [\fI<sym>\fP]
|
||||
.RS 4
|
||||
Specify a symbol to represent the current time.
|
||||
.RE
|
||||
.SH "EXIT CODES"
|
||||
.sp
|
||||
If \fBspacebar\fP can\(cqt handle a message, it will return a non\-zero exit code.
|
||||
.SH "AUTHOR"
|
||||
.sp
|
||||
Doron Somech <somdoron at gmail.com>
|
98
doc/spacebar.asciidoc
Normal file
98
doc/spacebar.asciidoc
Normal file
|
@ -0,0 +1,98 @@
|
|||
:man source: Spacebar
|
||||
:man version: {revnumber}
|
||||
:man manual: Spacebar Manual
|
||||
|
||||
ifdef::env-github[]
|
||||
:toc:
|
||||
:toc-title:
|
||||
:toc-placement!:
|
||||
:numbered:
|
||||
endif::[]
|
||||
|
||||
spacebar(1)
|
||||
===========
|
||||
|
||||
ifdef::env-github[]
|
||||
toc::[]
|
||||
endif::[]
|
||||
|
||||
Name
|
||||
----
|
||||
|
||||
spacebar - Status bar for mac
|
||||
|
||||
Synopsis
|
||||
--------
|
||||
|
||||
*spacebar* [*-v*,*--version*|*-V*,*--verbose*|*-m*,*--message* 'msg'|*-c*,*--config* 'config_file']
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
*spacebar* is a tiling window manager for macOS based on binary space partitioning.
|
||||
|
||||
Options
|
||||
-------
|
||||
*-v*, *--version*::
|
||||
Print the version and exit.
|
||||
|
||||
*-V*, *--verbose*::
|
||||
Output debug information to stdout.
|
||||
|
||||
*-m*, *--message* '<msg>'::
|
||||
Send message to a running instance of spacebar.
|
||||
|
||||
*-c*, *--config* '<config_file>'::
|
||||
Use the specified configuration file.
|
||||
|
||||
Config
|
||||
------
|
||||
|
||||
General Syntax
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
spacebar -m config <setting>::
|
||||
Get or set the value of <setting>.
|
||||
|
||||
Settings
|
||||
~~~~~~~~
|
||||
|
||||
*debug_output* ['<BOOL_SEL>']::
|
||||
Enable output of debug information to stdout.
|
||||
|
||||
*status_bar_text_font* ['<font_family>:<font_style>:<font_size>']::
|
||||
Specify name, style and size of font to use for drawing text. +
|
||||
Use 'Font Book.app' to identify the correct name.
|
||||
|
||||
*status_bar_icon_font* ['<font_family>:<font_style>:<font_size>']::
|
||||
Specify name, style and size of font to use for drawing icon symbols. +
|
||||
Use 'Font Book.app' to identify the correct name.
|
||||
|
||||
*status_bar_background_color* ['<COLOR>']::
|
||||
Color to use for drawing status bar background.
|
||||
|
||||
*status_bar_foreground_color* ['<COLOR>']::
|
||||
Color to use for drawing status bar elements.
|
||||
|
||||
*status_bar_space_icon_strip* ['<sym_1> <sym_2> <sym_n>']::
|
||||
Specify symbols separated by whitespace to be used for visualizing spaces.
|
||||
|
||||
*status_bar_power_icon_strip* ['<sym_battery> <sym_ac>']::
|
||||
Specify two symbols separated by whitespace. +
|
||||
The first symbol represents battery power and the second symbol indicates AC.
|
||||
|
||||
*status_bar_space_icon* ['<sym>']::
|
||||
Specify a general symbol to use for any given space that does not have a match in 'status_bar_space_icon_strip'.
|
||||
|
||||
*status_bar_clock_icon* ['<sym>']::
|
||||
Specify a symbol to represent the current time.
|
||||
|
||||
Exit Codes
|
||||
----------
|
||||
|
||||
If *spacebar* can't handle a message, it will return a non-zero exit code.
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
Doron Somech <somdoron at gmail.com>
|
12
examples/spacebarrc
Executable file
12
examples/spacebarrc
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
spacebar -m config status_bar_text_font "Helvetica Neue:Bold:12.0"
|
||||
spacebar -m config status_bar_icon_font "FontAwesome:Regular:12.0"
|
||||
spacebar -m config status_bar_background_color 0xff202020
|
||||
spacebar -m config status_bar_foreground_color 0xffa8a8a8
|
||||
spacebar -m config status_bar_space_icon_strip I II III IV V VI VII VIII IX X
|
||||
spacebar -m config status_bar_power_icon_strip
|
||||
spacebar -m config status_bar_space_icon
|
||||
spacebar -m config status_bar_clock_icon
|
||||
|
||||
echo "spacebar configuration loaded.."
|
46
makefile
Normal file
46
makefile
Normal file
|
@ -0,0 +1,46 @@
|
|||
FRAMEWORK_PATH = -F/System/Library/PrivateFrameworks
|
||||
FRAMEWORK = -framework Carbon -framework Cocoa -framework CoreServices -framework SkyLight -framework ScriptingBridge -framework IOKit
|
||||
BUILD_FLAGS = -std=c99 -Wall -DDEBUG -g -O0 -fvisibility=hidden -mmacosx-version-min=10.13
|
||||
BUILD_PATH = ./bin
|
||||
DOC_PATH = ./doc
|
||||
SCRIPT_PATH = ./scripts
|
||||
ASSET_PATH = ./assets
|
||||
SMP_PATH = ./examples
|
||||
ARCH_PATH = ./archive
|
||||
SPACEBAR_SRC = ./src/manifest.m
|
||||
BINS = $(BUILD_PATH)/spacebar
|
||||
|
||||
.PHONY: all clean install sign archive man
|
||||
|
||||
all: clean $(BINS)
|
||||
|
||||
install: BUILD_FLAGS=-std=c99 -Wall -DNDEBUG -O2 -fvisibility=hidden -mmacosx-version-min=10.13
|
||||
install: clean $(BINS)
|
||||
|
||||
stats: BUILD_FLAGS=-std=c99 -Wall -DSTATS -DNDEBUG -O2 -fvisibility=hidden -mmacosx-version-min=10.13
|
||||
stats: clean $(BINS)
|
||||
|
||||
man:
|
||||
asciidoctor -b manpage $(DOC_PATH)/spacebar.asciidoc -o $(DOC_PATH)/spacebar.1
|
||||
|
||||
icon:
|
||||
python $(SCRIPT_PATH)/seticon.py $(ASSET_PATH)/icon/2x/icon-512px@2x.png $(BUILD_PATH)/spacebar
|
||||
|
||||
archive: man install sign icon
|
||||
rm -rf $(ARCH_PATH)
|
||||
mkdir -p $(ARCH_PATH)
|
||||
cp -r $(BUILD_PATH) $(ARCH_PATH)/
|
||||
cp -r $(DOC_PATH) $(ARCH_PATH)/
|
||||
cp -r $(SMP_PATH) $(ARCH_PATH)/
|
||||
tar -cvzf $(BUILD_PATH)/$(shell $(BUILD_PATH)/spacebar --version).tar.gz $(ARCH_PATH)
|
||||
rm -rf $(ARCH_PATH)
|
||||
|
||||
sign:
|
||||
codesign -fs "spacebar-cert" $(BUILD_PATH)/spacebar
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_PATH)
|
||||
|
||||
$(BUILD_PATH)/spacebar: $(SPACEBAR_SRC)
|
||||
mkdir -p $(BUILD_PATH)
|
||||
clang $^ $(BUILD_FLAGS) $(FRAMEWORK_PATH) $(FRAMEWORK) -o $@
|
47
scripts/codesign
Executable file
47
scripts/codesign
Executable file
|
@ -0,0 +1,47 @@
|
|||
#! /usr/bin/env bash
|
||||
|
||||
identities="$(security find-identity -v -p codesigning | sed '$d')"
|
||||
id_count="$(echo "${identities}" | wc -l)"
|
||||
target="$(command -v "${1:-}")"
|
||||
|
||||
function error() {
|
||||
{
|
||||
echo "$(tput bold)Error: ${@}$(tput sgr0)"
|
||||
echo
|
||||
echo "Usage: ./scripts/codesign <path/to/executable> [<certificate>]"
|
||||
echo "Available codesigning certificates:"
|
||||
echo "${identities}"
|
||||
} >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "${id_count}" -lt 1 ]; then
|
||||
>&2 echo "Unable to find a codesigning identity"
|
||||
exit 1
|
||||
elif [ "${id_count}" -eq 1 ]; then
|
||||
selection="1"
|
||||
elif [ "${id_count}" -gt 1 ] && [ "${#}" -eq 2 ]; then
|
||||
selection="${2}"
|
||||
elif [ -x "${target}" ]; then
|
||||
error "Unable to auto-select codesigning certificate"
|
||||
else
|
||||
error "Unable to find executable \"${target}\""
|
||||
fi
|
||||
|
||||
if [[ "${selection}" =~ ^[0-9]+$ ]]; then
|
||||
certificate="$(echo "${identities}" \
|
||||
| awk "NR==${selection} {print \$2}")"
|
||||
else
|
||||
certificate="$(echo "${identities}" \
|
||||
| awk 'BEGIN{FS=OFS="\""} {gsub(/ /,"_",$2)} 1' \
|
||||
| awk "\$3 ~ /${selection// /_}/ {print \$2}")"
|
||||
fi
|
||||
|
||||
if [ -z "${certificate}" ]; then
|
||||
error "Unable to find codesigning certificate \"${selection}\""
|
||||
elif [ "$(echo "${certificate}" | wc -l)" -ne 1 ]; then
|
||||
error "Unable to uniquely identify codesigning certificate \"${selection}\""
|
||||
fi
|
||||
|
||||
command codesign --deep --force --verbose --sign "${certificate}" "${target}"
|
||||
|
95
src/application.c
Normal file
95
src/application.c
Normal file
|
@ -0,0 +1,95 @@
|
|||
#include "application.h"
|
||||
|
||||
extern struct event_loop g_event_loop;
|
||||
|
||||
static OBSERVER_CALLBACK(application_notification_handler)
|
||||
{
|
||||
if (CFEqual(notification, kAXFocusedWindowChangedNotification)) {
|
||||
uint32_t window_id = ax_window_id(element);
|
||||
if (!window_id) return;
|
||||
|
||||
struct event *event = event_create(&g_event_loop, WINDOW_FOCUSED, (void *)(intptr_t) window_id);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
} else if (CFEqual(notification, kAXTitleChangedNotification)) {
|
||||
uint32_t window_id = ax_window_id(element);
|
||||
if (!window_id) return;
|
||||
|
||||
struct event *event = event_create(&g_event_loop, WINDOW_TITLE_CHANGED, (void *)(intptr_t) window_id);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
application_observe_notification(struct application *application, int notification)
|
||||
{
|
||||
AXError result = AXObserverAddNotification(application->observer_ref, application->ref, ax_application_notification[notification], application);
|
||||
if (result == kAXErrorSuccess || result == kAXErrorNotificationAlreadyRegistered) {
|
||||
application->notification |= 1 << notification;
|
||||
} else if (result != kAXErrorNotImplemented) {
|
||||
application->retry = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
application_unobserve_notification(struct application *application, int notification)
|
||||
{
|
||||
AXObserverRemoveNotification(application->observer_ref, application->ref, ax_application_notification[notification]);
|
||||
application->notification &= ~(1 << notification);
|
||||
}
|
||||
|
||||
bool application_observe(struct application *application)
|
||||
{
|
||||
if (AXObserverCreate(application->pid, application_notification_handler, &application->observer_ref) == kAXErrorSuccess) {
|
||||
for (int i = 0; i < array_count(ax_application_notification); ++i) {
|
||||
application_observe_notification(application, i);
|
||||
}
|
||||
|
||||
application->is_observing = true;
|
||||
CFRunLoopAddSource(CFRunLoopGetMain(), AXObserverGetRunLoopSource(application->observer_ref), kCFRunLoopDefaultMode);
|
||||
}
|
||||
|
||||
return (application->notification & AX_APPLICATION_ALL) == AX_APPLICATION_ALL;
|
||||
}
|
||||
|
||||
void application_unobserve(struct application *application)
|
||||
{
|
||||
if (application->is_observing) {
|
||||
for (int i = 0; i < array_count(ax_application_notification); ++i) {
|
||||
if (!(application->notification & (1 << i))) continue;
|
||||
application_unobserve_notification(application, i);
|
||||
}
|
||||
|
||||
application->is_observing = false;
|
||||
CFRunLoopSourceInvalidate(AXObserverGetRunLoopSource(application->observer_ref));
|
||||
CFRelease(application->observer_ref);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t application_focused_window(struct application *application)
|
||||
{
|
||||
CFTypeRef window_ref = NULL;
|
||||
AXUIElementCopyAttributeValue(application->ref, kAXFocusedWindowAttribute, &window_ref);
|
||||
if (!window_ref) return 0;
|
||||
|
||||
uint32_t window_id = ax_window_id(window_ref);
|
||||
CFRelease(window_ref);
|
||||
|
||||
return window_id;
|
||||
}
|
||||
|
||||
struct application *application_create(struct process *process)
|
||||
{
|
||||
struct application *application = malloc(sizeof(struct application));
|
||||
memset(application, 0, sizeof(struct application));
|
||||
application->ref = AXUIElementCreateApplication(process->pid);
|
||||
application->psn = process->psn;
|
||||
application->pid = process->pid;
|
||||
application->name = process->name;
|
||||
return application;
|
||||
}
|
||||
|
||||
void application_destroy(struct application *application)
|
||||
{
|
||||
CFRelease(application->ref);
|
||||
free(application);
|
||||
}
|
38
src/application.h
Normal file
38
src/application.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef APPLICATION_H
|
||||
#define APPLICATION_H
|
||||
|
||||
#define OBSERVER_CALLBACK(name) void name(AXObserverRef observer, AXUIElementRef element, CFStringRef notification, void *context)
|
||||
typedef OBSERVER_CALLBACK(observer_callback);
|
||||
|
||||
#define AX_APPLICATION_WINDOW_FOCUSED_INDEX 0
|
||||
#define AX_APPLICATION_WINDOW_TITLE_CHANGED_INDEX 1
|
||||
|
||||
#define AX_APPLICATION_WINDOW_FOCUSED (1 << AX_APPLICATION_WINDOW_FOCUSED_INDEX)
|
||||
#define AX_APPLICATION_WINDOW_TITLE_CHANGED (1 << AX_APPLICATION_WINDOW_TITLE_CHANGED_INDEX)
|
||||
#define AX_APPLICATION_ALL (AX_APPLICATION_WINDOW_FOCUSED |\
|
||||
AX_APPLICATION_WINDOW_TITLE_CHANGED)
|
||||
static CFStringRef ax_application_notification[] =
|
||||
{
|
||||
[AX_APPLICATION_WINDOW_FOCUSED_INDEX] = kAXFocusedWindowChangedNotification,
|
||||
[AX_APPLICATION_WINDOW_TITLE_CHANGED_INDEX] = kAXTitleChangedNotification,
|
||||
};
|
||||
|
||||
struct application
|
||||
{
|
||||
AXUIElementRef ref;
|
||||
ProcessSerialNumber psn;
|
||||
uint32_t pid;
|
||||
char *name;
|
||||
AXObserverRef observer_ref;
|
||||
uint8_t notification;
|
||||
bool is_observing;
|
||||
bool retry;
|
||||
};
|
||||
|
||||
uint32_t application_focused_window(struct application *application);
|
||||
bool application_observe(struct application *application);
|
||||
void application_unobserve(struct application *application);
|
||||
struct application *application_create(struct process *process);
|
||||
void application_destroy(struct application *application);
|
||||
|
||||
#endif
|
81
src/application_manager.c
Normal file
81
src/application_manager.c
Normal file
|
@ -0,0 +1,81 @@
|
|||
#include "application_manager.h"
|
||||
|
||||
extern struct process_manager g_process_manager;
|
||||
extern struct mouse_state g_mouse_state;
|
||||
extern char g_sa_socket_file[MAXLEN];
|
||||
|
||||
static TABLE_HASH_FUNC(hash_application)
|
||||
{
|
||||
unsigned long result = *(uint32_t *) key;
|
||||
result = (result + 0x7ed55d16) + (result << 12);
|
||||
result = (result ^ 0xc761c23c) ^ (result >> 19);
|
||||
result = (result + 0x165667b1) + (result << 5);
|
||||
result = (result + 0xd3a2646c) ^ (result << 9);
|
||||
result = (result + 0xfd7046c5) + (result << 3);
|
||||
result = (result ^ 0xb55a4f09) ^ (result >> 16);
|
||||
return result;
|
||||
}
|
||||
|
||||
static TABLE_COMPARE_FUNC(compare_application)
|
||||
{
|
||||
return *(uint32_t *) key_a == *(uint32_t *) key_b;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
struct application *application_manager_focused_application(struct application_manager *application_manager)
|
||||
{
|
||||
ProcessSerialNumber psn = {};
|
||||
_SLPSGetFrontProcess(&psn);
|
||||
|
||||
pid_t pid;
|
||||
GetProcessPID(&psn, &pid);
|
||||
|
||||
return application_manager_find_application(application_manager, pid);
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
struct application *application_manager_find_application(struct application_manager *application_manager, pid_t pid)
|
||||
{
|
||||
return table_find(&application_manager->application, &pid);
|
||||
}
|
||||
|
||||
void application_manager_remove_application(struct application_manager *application_manager, pid_t pid)
|
||||
{
|
||||
table_remove(&application_manager->application, &pid);
|
||||
}
|
||||
|
||||
void application_manager_add_application(struct application_manager *application_manager, struct application *application)
|
||||
{
|
||||
table_add(&application_manager->application, &application->pid, application);
|
||||
}
|
||||
|
||||
void application_manager_init(struct application_manager *application_manager)
|
||||
{
|
||||
application_manager->system_element = AXUIElementCreateSystemWide();
|
||||
AXUIElementSetMessagingTimeout(application_manager->system_element, 1.0);
|
||||
|
||||
table_init(&application_manager->application, 150, hash_application, compare_application);
|
||||
}
|
||||
|
||||
void application_manager_begin(struct application_manager *application_manager)
|
||||
{
|
||||
for (int process_index = 0; process_index < g_process_manager.process.capacity; ++process_index) {
|
||||
struct bucket *bucket = g_process_manager.process.buckets[process_index];
|
||||
while (bucket) {
|
||||
if (bucket->value) {
|
||||
struct process *process = bucket->value;
|
||||
struct application *application = application_create(process);
|
||||
|
||||
if (application_observe(application)) {
|
||||
application_manager_add_application(application_manager, application);
|
||||
} else {
|
||||
application_unobserve(application);
|
||||
application_destroy(application);
|
||||
}
|
||||
}
|
||||
|
||||
bucket = bucket->next;
|
||||
}
|
||||
}
|
||||
}
|
35
src/application_manager.h
Normal file
35
src/application_manager.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef APPLICATION_MANAGER_H
|
||||
#define APPLICATION_MANAGER_H
|
||||
|
||||
extern CFTypeRef SLSWindowQueryWindows(int cid, CFArrayRef windows, int count);
|
||||
extern CFTypeRef SLSWindowQueryResultCopyWindows(CFTypeRef window_query);
|
||||
extern CGError SLSWindowIteratorAdvance(CFTypeRef iterator);
|
||||
extern uint32_t SLSWindowIteratorGetParentID(CFTypeRef iterator);
|
||||
extern uint32_t SLSWindowIteratorGetWindowID(CFTypeRef iterator);
|
||||
extern OSStatus _SLPSGetFrontProcess(ProcessSerialNumber *psn);
|
||||
extern CGError SLSGetWindowOwner(int cid, uint32_t wid, int *wcid);
|
||||
extern CGError SLSGetConnectionPSN(int cid, ProcessSerialNumber *psn);
|
||||
extern CGError SLSConnectionGetPID(int cid, pid_t *pid);
|
||||
extern CGError _SLPSSetFrontProcessWithOptions(ProcessSerialNumber *psn, uint32_t wid, uint32_t mode);
|
||||
extern CGError SLPSPostEventRecordTo(ProcessSerialNumber *psn, uint8_t *bytes);
|
||||
extern OSStatus SLSFindWindowByGeometry(int cid, int zero, int one, int zero_again, CGPoint *screen_point, CGPoint *window_point, uint32_t *wid, int *wcid);
|
||||
extern CGError SLSGetCurrentCursorLocation(int cid, CGPoint *point);
|
||||
|
||||
#define kCPSAllWindows 0x100
|
||||
#define kCPSUserGenerated 0x200
|
||||
#define kCPSNoWindows 0x400
|
||||
|
||||
struct application_manager
|
||||
{
|
||||
AXUIElementRef system_element;
|
||||
struct table application;
|
||||
};
|
||||
|
||||
struct application *application_manager_focused_application(struct application_manager *application_manager);
|
||||
struct application *application_manager_find_application(struct application_manager *application_manager, pid_t pid);
|
||||
void application_manager_remove_application(struct application_manager *application_manager, pid_t pid);
|
||||
void application_manager_add_application(struct application_manager *application_manager, struct application *application);
|
||||
void application_manager_begin(struct application_manager *application_manager);
|
||||
void application_manager_init(struct application_manager *application_manager);
|
||||
|
||||
#endif
|
418
src/bar.c
Normal file
418
src/bar.c
Normal file
|
@ -0,0 +1,418 @@
|
|||
#include "bar.h"
|
||||
|
||||
extern struct event_loop g_event_loop;
|
||||
//extern struct space_manager g_space_manager;
|
||||
extern struct bar_manager g_bar_manager;
|
||||
|
||||
static POWER_CALLBACK(power_handler)
|
||||
{
|
||||
struct event *event = event_create(&g_event_loop, BAR_REFRESH, NULL);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
}
|
||||
|
||||
static TIMER_CALLBACK(timer_handler)
|
||||
{
|
||||
struct event *event = event_create(&g_event_loop, BAR_REFRESH, NULL);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
}
|
||||
|
||||
static int bar_find_battery_life(bool *has_battery, bool *charging)
|
||||
{
|
||||
CFTypeRef ps_info = IOPSCopyPowerSourcesInfo();
|
||||
CFTypeRef ps_list = IOPSCopyPowerSourcesList(ps_info);
|
||||
|
||||
int ps_count = CFArrayGetCount(ps_list);
|
||||
if (!ps_count) return 0;
|
||||
|
||||
int cur_capacity = 0;
|
||||
int max_capacity = 0;
|
||||
int percent = 0;
|
||||
|
||||
for (int i = 0; i < ps_count; ++i) {
|
||||
CFDictionaryRef ps = IOPSGetPowerSourceDescription(ps_info, CFArrayGetValueAtIndex(ps_list, i));
|
||||
if (!ps) continue;
|
||||
|
||||
CFTypeRef ps_type = CFDictionaryGetValue(ps, CFSTR(kIOPSTypeKey));
|
||||
if (!ps_type || !CFEqual(ps_type, CFSTR(kIOPSInternalBatteryType))) continue;
|
||||
|
||||
CFTypeRef ps_cur = CFDictionaryGetValue(ps, CFSTR(kIOPSCurrentCapacityKey));
|
||||
if (!ps_cur) continue;
|
||||
|
||||
CFTypeRef ps_max = CFDictionaryGetValue(ps, CFSTR(kIOPSMaxCapacityKey));
|
||||
if (!ps_max) continue;
|
||||
|
||||
CFTypeRef ps_charging = CFDictionaryGetValue(ps, CFSTR(kIOPSPowerSourceStateKey));
|
||||
if (!ps_charging) continue;
|
||||
|
||||
CFNumberGetValue((CFNumberRef) ps_cur, kCFNumberSInt32Type, &cur_capacity);
|
||||
CFNumberGetValue((CFNumberRef) ps_max, kCFNumberSInt32Type, &max_capacity);
|
||||
*charging = !CFEqual(ps_charging, CFSTR(kIOPSBatteryPowerValue));
|
||||
*has_battery = true;
|
||||
percent = (int)((double) cur_capacity / (double) max_capacity * 100);
|
||||
break;
|
||||
}
|
||||
|
||||
CFRelease(ps_list);
|
||||
CFRelease(ps_info);
|
||||
return percent;
|
||||
}
|
||||
|
||||
static CTFontRef bar_create_font(char *cstring)
|
||||
{
|
||||
float size = 10.0f;
|
||||
char font_properties[2][255] = { {}, {} };
|
||||
sscanf(cstring, "%254[^:]:%254[^:]:%f", font_properties[0], font_properties[1], &size);
|
||||
CFStringRef font_family_name = CFStringCreateWithCString(NULL, font_properties[0], kCFStringEncodingUTF8);
|
||||
CFStringRef font_style_name = CFStringCreateWithCString(NULL, font_properties[1], kCFStringEncodingUTF8);
|
||||
CFNumberRef font_size = CFNumberCreate(NULL, kCFNumberFloat32Type, &size);
|
||||
|
||||
const void *keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute, kCTFontSizeAttribute };
|
||||
const void *values[] = { font_family_name, font_style_name, font_size };
|
||||
CFDictionaryRef attributes = CFDictionaryCreate(NULL, keys, values, array_count(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes(attributes);
|
||||
CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, 0.0, NULL);
|
||||
|
||||
CFRelease(descriptor);
|
||||
CFRelease(attributes);
|
||||
CFRelease(font_size);
|
||||
CFRelease(font_style_name);
|
||||
CFRelease(font_family_name);
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
static CGPoint bar_align_line(struct bar *bar, struct bar_line line, int align_x, int align_y)
|
||||
{
|
||||
float x = 0, y = 0;
|
||||
|
||||
if (align_x == ALIGN_NONE) {
|
||||
x = CGContextGetTextPosition(bar->context).x;
|
||||
} else if (align_x == ALIGN_LEFT) {
|
||||
x = 20;
|
||||
} else if (align_x == ALIGN_CENTER) {
|
||||
x = (bar->frame.size.width / 2) - (line.bounds.size.width / 2);
|
||||
} else if (align_x == ALIGN_RIGHT) {
|
||||
x = bar->frame.size.width - line.bounds.size.width - 20;
|
||||
}
|
||||
|
||||
if (align_y == ALIGN_NONE) {
|
||||
y = CGContextGetTextPosition(bar->context).y;
|
||||
} else if (align_y == ALIGN_TOP) {
|
||||
y = bar->frame.size.height;
|
||||
} else if (align_y == ALIGN_CENTER) {
|
||||
y = (bar->frame.size.height / 2) - ((line.ascent - line.descent) / 2);
|
||||
} else if (align_y == ALIGN_BOTTOM) {
|
||||
y = line.descent;
|
||||
}
|
||||
|
||||
return (CGPoint) { x, y };
|
||||
}
|
||||
|
||||
static void bar_draw_line(struct bar *bar, struct bar_line line, float x, float y)
|
||||
{
|
||||
CGContextSetRGBFillColor(bar->context, line.color.r, line.color.g, line.color.b, line.color.a);
|
||||
CGContextSetTextPosition(bar->context, x, y);
|
||||
CTLineDraw(line.line, bar->context);
|
||||
}
|
||||
|
||||
static void bar_destroy_line(struct bar_line line)
|
||||
{
|
||||
CFRelease(line.line);
|
||||
}
|
||||
|
||||
static struct bar_line bar_prepare_line(CTFontRef font, char *cstring, struct rgba_color color)
|
||||
{
|
||||
const void *keys[] = { kCTFontAttributeName, kCTForegroundColorFromContextAttributeName };
|
||||
const void *values[] = { font, kCFBooleanTrue };
|
||||
CFDictionaryRef attributes = CFDictionaryCreate(NULL, keys, values, array_count(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
CFStringRef string = CFStringCreateWithCString(NULL, cstring, kCFStringEncodingUTF8);
|
||||
CFAttributedStringRef attr_string = CFAttributedStringCreate(NULL, string, attributes);
|
||||
CTLineRef line = CTLineCreateWithAttributedString(attr_string);
|
||||
|
||||
CGFloat ascent, descent;
|
||||
CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
|
||||
CGRect bounds = CTLineGetBoundsWithOptions(line, kCTLineBoundsUseGlyphPathBounds);
|
||||
|
||||
CFRelease(string);
|
||||
CFRelease(attributes);
|
||||
CFRelease(attr_string);
|
||||
|
||||
return (struct bar_line) {
|
||||
.line = line,
|
||||
.ascent = ascent,
|
||||
.descent = descent,
|
||||
.bounds = bounds,
|
||||
.color = color
|
||||
};
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
static char * focused_window_title()
|
||||
{
|
||||
ProcessSerialNumber psn = {};
|
||||
_SLPSGetFrontProcess(&psn);
|
||||
|
||||
pid_t pid;
|
||||
GetProcessPID(&psn, &pid);
|
||||
|
||||
AXUIElementRef application_ref = AXUIElementCreateApplication(pid);
|
||||
if (!application_ref)
|
||||
return NULL;
|
||||
|
||||
CFTypeRef window_ref = NULL;
|
||||
AXUIElementCopyAttributeValue(application_ref, kAXFocusedWindowAttribute, &window_ref);
|
||||
if (!window_ref) {
|
||||
CFRelease(application_ref);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *title = NULL;
|
||||
CFTypeRef value = NULL;
|
||||
AXUIElementCopyAttributeValue(window_ref, kAXTitleAttribute, &value);
|
||||
if (value) {
|
||||
title = cfstring_copy(value);
|
||||
CFRelease(value);
|
||||
}
|
||||
|
||||
CFRelease(window_ref);
|
||||
CFRelease(application_ref);
|
||||
|
||||
return title;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
void bar_refresh(struct bar *bar)
|
||||
{
|
||||
SLSDisableUpdate(g_connection);
|
||||
SLSOrderWindow(g_connection, bar->id, -1, 0);
|
||||
CGContextClearRect(bar->context, bar->frame);
|
||||
CGContextSetRGBFillColor(bar->context, g_bar_manager.background_color.r, g_bar_manager.background_color.g, g_bar_manager.background_color.b, g_bar_manager.background_color.a);
|
||||
CGContextFillRect(bar->context, bar->frame);
|
||||
CGContextStrokePath(bar->context);
|
||||
|
||||
//
|
||||
// BAR LEFT
|
||||
//
|
||||
|
||||
int final_bar_left_x = 10;
|
||||
int space_count;
|
||||
uint64_t *space_list = display_space_list(bar->did, &space_count);
|
||||
if (space_list) {
|
||||
uint64_t sid = display_space_id(bar->did);
|
||||
|
||||
for (int i = 0; i < space_count; ++i) {
|
||||
CGPoint pos = CGContextGetTextPosition(bar->context);
|
||||
struct bar_line space_line = i >= buf_len(g_bar_manager.space_icon_strip)
|
||||
? g_bar_manager.space_icon
|
||||
: g_bar_manager.space_icon_strip[i];
|
||||
if (i == 0) {
|
||||
pos = bar_align_line(bar, space_line, ALIGN_LEFT, ALIGN_CENTER);
|
||||
} else {
|
||||
pos.x += 25;
|
||||
}
|
||||
|
||||
bar_draw_line(bar, space_line, pos.x, pos.y);
|
||||
|
||||
if (sid == space_list[i]) {
|
||||
CGPoint new_pos = CGContextGetTextPosition(bar->context);
|
||||
struct bar_line mark_line = g_bar_manager.space_underline;
|
||||
CGPoint mark_pos = bar_align_line(bar, mark_line, 0, ALIGN_BOTTOM);
|
||||
mark_pos.x = mark_pos.x - mark_line.bounds.size.width / 2 - space_line.bounds.size.width / 2;
|
||||
bar_draw_line(bar, mark_line, mark_pos.x, mark_pos.y);
|
||||
CGContextSetTextPosition(bar->context, new_pos.x, new_pos.y);
|
||||
}
|
||||
|
||||
final_bar_left_x = pos.x + space_line.bounds.size.width + 10;
|
||||
}
|
||||
|
||||
free(space_list);
|
||||
}
|
||||
|
||||
//
|
||||
// BAR RIGHT
|
||||
//
|
||||
|
||||
int initial_bar_right_x = bar->frame.size.width - 10;
|
||||
time_t rawtime;
|
||||
time(&rawtime);
|
||||
float time_line_width = 0;
|
||||
struct tm *timeinfo = localtime(&rawtime);
|
||||
if (timeinfo) {
|
||||
char time[255];
|
||||
snprintf(time, sizeof(time), "%02d:%02d", timeinfo->tm_hour, timeinfo->tm_min);
|
||||
struct bar_line time_line = bar_prepare_line(g_bar_manager.t_font, time, g_bar_manager.foreground_color);
|
||||
CGPoint t_pos = bar_align_line(bar, time_line, ALIGN_RIGHT, ALIGN_CENTER);
|
||||
bar_draw_line(bar, time_line, t_pos.x, t_pos.y);
|
||||
|
||||
CGPoint ti_pos = bar_align_line(bar, g_bar_manager.clock_icon, 0, ALIGN_CENTER);
|
||||
ti_pos.x = t_pos.x - g_bar_manager.clock_icon.bounds.size.width - 5;
|
||||
|
||||
CGPoint tu_pos = bar_align_line(bar, g_bar_manager.clock_underline, 0, ALIGN_BOTTOM);
|
||||
tu_pos.x = tu_pos.x - g_bar_manager.clock_underline.bounds.size.width / 2 - time_line.bounds.size.width / 2 - (g_bar_manager.clock_icon.bounds.size.width + 5) / 2;
|
||||
|
||||
bar_draw_line(bar, g_bar_manager.clock_icon, ti_pos.x, ti_pos.y);
|
||||
bar_draw_line(bar, g_bar_manager.clock_underline, tu_pos.x, tu_pos.y);
|
||||
bar_destroy_line(time_line);
|
||||
|
||||
initial_bar_right_x = tu_pos.x - 10;
|
||||
}
|
||||
|
||||
bool has_batt = false;
|
||||
bool charging = false;
|
||||
int percent = bar_find_battery_life(&has_batt, &charging);
|
||||
if (has_batt) {
|
||||
char batt[255];
|
||||
snprintf(batt, sizeof(batt), "%' '3d%%", percent);
|
||||
|
||||
struct bar_line batt_line = bar_prepare_line(g_bar_manager.t_font, batt, g_bar_manager.foreground_color);
|
||||
CGPoint p_pos = bar_align_line(bar, batt_line, ALIGN_RIGHT, ALIGN_CENTER);
|
||||
p_pos.x = p_pos.x - time_line_width - g_bar_manager.clock_underline.bounds.size.width - 20;
|
||||
bar_draw_line(bar, batt_line, p_pos.x, p_pos.y);
|
||||
|
||||
struct bar_line batt_icon = charging ? g_bar_manager.power_icon : g_bar_manager.battr_icon;
|
||||
CGPoint pi_pos = bar_align_line(bar, batt_icon, 0, ALIGN_CENTER);
|
||||
pi_pos.x = p_pos.x - batt_icon.bounds.size.width - 5;
|
||||
|
||||
CGPoint pu_pos = bar_align_line(bar, g_bar_manager.power_underline, 0, ALIGN_BOTTOM);
|
||||
pu_pos.x = pu_pos.x - g_bar_manager.power_underline.bounds.size.width / 2 - batt_line.bounds.size.width / 2 - (batt_icon.bounds.size.width + 5) / 2;
|
||||
|
||||
bar_draw_line(bar, batt_icon, pi_pos.x, pi_pos.y);
|
||||
bar_draw_line(bar, g_bar_manager.power_underline, pu_pos.x, pu_pos.y);
|
||||
bar_destroy_line(batt_line);
|
||||
|
||||
initial_bar_right_x = pu_pos.x - 10;
|
||||
}
|
||||
|
||||
// BAR CENTER
|
||||
char *title = focused_window_title();
|
||||
if (title) {
|
||||
int overlap_left = 0;
|
||||
int overlap_right = 0;
|
||||
|
||||
struct bar_line title_line = bar_prepare_line(g_bar_manager.t_font, title, g_bar_manager.foreground_color);
|
||||
CGPoint pos = bar_align_line(bar, title_line, ALIGN_CENTER, ALIGN_CENTER);
|
||||
|
||||
if (final_bar_left_x >= pos.x) {
|
||||
overlap_left = final_bar_left_x - pos.x;
|
||||
}
|
||||
|
||||
assert(overlap_left >= 0);
|
||||
|
||||
if (overlap_left > 0) {
|
||||
pos.x = final_bar_left_x;
|
||||
}
|
||||
|
||||
if (initial_bar_right_x <= pos.x + title_line.bounds.size.width) {
|
||||
overlap_right = pos.x + title_line.bounds.size.width - initial_bar_right_x;
|
||||
}
|
||||
|
||||
assert(overlap_right >= 0);
|
||||
|
||||
if (overlap_right > 0) {
|
||||
int truncated_width = (int)title_line.bounds.size.width - overlap_right;
|
||||
if (truncated_width > 0) {
|
||||
CTLineRef truncated_line = CTLineCreateTruncatedLine(title_line.line, truncated_width, kCTLineTruncationEnd, NULL);
|
||||
CFRelease(title_line.line);
|
||||
title_line.line = truncated_line;
|
||||
} else {
|
||||
goto free_title;
|
||||
}
|
||||
}
|
||||
|
||||
bar_draw_line(bar, title_line, pos.x, pos.y);
|
||||
free_title:
|
||||
bar_destroy_line(title_line);
|
||||
free(title);
|
||||
}
|
||||
|
||||
CGContextFlush(bar->context);
|
||||
SLSOrderWindow(g_connection, bar->id, 1, bar->id);
|
||||
SLSReenableUpdate(g_connection);
|
||||
}
|
||||
|
||||
static CGPoint bar_create_frame(struct bar *bar, CFTypeRef *frame_region)
|
||||
{
|
||||
CGRect bounds = display_bounds(bar->did);
|
||||
CGPoint origin = bounds.origin;
|
||||
|
||||
if (!display_manager_menu_bar_hidden()) {
|
||||
CGRect menu = display_manager_menu_bar_rect(bar->did);
|
||||
origin.y += menu.size.height;
|
||||
}
|
||||
|
||||
bar->frame = (CGRect) {{0, 0},{bounds.size.width, 26}};
|
||||
CGSNewRegionWithRect(&bar->frame, frame_region);
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
void bar_resize(struct bar *bar)
|
||||
{
|
||||
CFTypeRef frame_region;
|
||||
CGPoint origin = bar_create_frame(bar, &frame_region);
|
||||
|
||||
SLSDisableUpdate(g_connection);
|
||||
SLSOrderWindow(g_connection, bar->id, -1, 0);
|
||||
SLSSetWindowShape(g_connection, bar->id, origin.x, origin.y, frame_region);
|
||||
bar_refresh(bar);
|
||||
SLSOrderWindow(g_connection, bar->id, 1, 0);
|
||||
SLSReenableUpdate(g_connection);
|
||||
CFRelease(frame_region);
|
||||
}
|
||||
|
||||
struct bar *bar_create(uint32_t did)
|
||||
{
|
||||
struct bar *bar = malloc(sizeof(struct bar));
|
||||
memset(bar, 0, sizeof(struct bar));
|
||||
bar->did = did;
|
||||
|
||||
uint32_t set_tags[2] = {
|
||||
kCGSStickyTagBit |
|
||||
kCGSModalWindowTagBit |
|
||||
kCGSDisableShadowTagBit |
|
||||
kCGSHighQualityResamplingTagBit |
|
||||
kCGSIgnoreForExposeTagBit
|
||||
};
|
||||
|
||||
uint32_t clear_tags[2] = { 0, 0 };
|
||||
*((int8_t *)(clear_tags) + 0x5) = 0x20;
|
||||
|
||||
CFTypeRef frame_region;
|
||||
CGPoint origin = bar_create_frame(bar, &frame_region);
|
||||
|
||||
SLSNewWindow(g_connection, 2, origin.x, origin.y, frame_region, &bar->id);
|
||||
CFRelease(frame_region);
|
||||
|
||||
SLSSetWindowResolution(g_connection, bar->id, 2.0f);
|
||||
SLSSetWindowTags(g_connection, bar->id, set_tags, 64);
|
||||
SLSClearWindowTags(g_connection, bar->id, clear_tags, 64);
|
||||
SLSSetWindowOpacity(g_connection, bar->id, 0);
|
||||
SLSSetMouseEventEnableFlags(g_connection, bar->id, false);
|
||||
SLSSetWindowLevel(g_connection, bar->id, CGWindowLevelForKey(4));
|
||||
bar->context = SLWindowContextCreate(g_connection, bar->id, 0);
|
||||
|
||||
int refresh_frequency = 5;
|
||||
bar->power_source = IOPSNotificationCreateRunLoopSource(power_handler, NULL);
|
||||
bar->refresh_timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + refresh_frequency, refresh_frequency, 0, 0, timer_handler, NULL);
|
||||
|
||||
CFRunLoopAddSource(CFRunLoopGetMain(), bar->power_source, kCFRunLoopCommonModes);
|
||||
CFRunLoopAddTimer(CFRunLoopGetMain(), bar->refresh_timer, kCFRunLoopCommonModes);
|
||||
|
||||
bar_refresh(bar);
|
||||
|
||||
return bar;
|
||||
}
|
||||
|
||||
void bar_destroy(struct bar *bar)
|
||||
{
|
||||
CFRunLoopRemoveSource(CFRunLoopGetMain(), bar->power_source, kCFRunLoopCommonModes);
|
||||
CFRunLoopSourceInvalidate(bar->power_source);
|
||||
|
||||
CFRunLoopRemoveTimer(CFRunLoopGetMain(), bar->refresh_timer, kCFRunLoopCommonModes);
|
||||
CFRunLoopTimerInvalidate(bar->refresh_timer);
|
||||
|
||||
CGContextRelease(bar->context);
|
||||
SLSReleaseWindow(g_connection, bar->id);
|
||||
free(bar);
|
||||
}
|
62
src/bar.h
Normal file
62
src/bar.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
#ifndef BAR_H
|
||||
#define BAR_H
|
||||
|
||||
extern CGError SLSDisableUpdate(int cid);
|
||||
extern CGError SLSReenableUpdate(int cid);
|
||||
extern CGError SLSNewWindow(int cid, int type, float x, float y, CFTypeRef region, uint32_t *wid);
|
||||
extern CGError SLSReleaseWindow(int cid, uint32_t wid);
|
||||
extern CGError SLSSetWindowTags(int cid, uint32_t wid, uint32_t tags[2], int tag_size);
|
||||
extern CGError SLSClearWindowTags(int cid, uint32_t wid, uint32_t tags[2], int tag_size);
|
||||
extern CGError SLSSetWindowShape(int cid, uint32_t wid, float x_offset, float y_offset, CFTypeRef shape);
|
||||
extern CGError SLSSetWindowResolution(int cid, uint32_t wid, double res);
|
||||
extern CGError SLSSetWindowOpacity(int cid, uint32_t wid, bool isOpaque);
|
||||
extern CGError SLSSetMouseEventEnableFlags(int cid, uint32_t wid, bool shouldEnable);
|
||||
extern CGError SLSOrderWindow(int cid, uint32_t wid, int mode, uint32_t relativeToWID);
|
||||
extern CGError SLSSetWindowLevel(int cid, uint32_t wid, int level);
|
||||
extern CGContextRef SLWindowContextCreate(int cid, uint32_t wid, CFDictionaryRef options);
|
||||
extern CGError CGSNewRegionWithRect(CGRect *rect, CFTypeRef *outRegion);
|
||||
|
||||
#define kCGSModalWindowTagBit (1 << 31)
|
||||
#define kCGSDisableShadowTagBit (1 << 3)
|
||||
#define kCGSHighQualityResamplingTagBit (1 << 4)
|
||||
#define kCGSIgnoreForExposeTagBit (1 << 7)
|
||||
#define kCGSStickyTagBit (1 << 11)
|
||||
|
||||
#define POWER_CALLBACK(name) void name(void *context)
|
||||
typedef POWER_CALLBACK(power_callback);
|
||||
|
||||
#define TIMER_CALLBACK(name) void name(CFRunLoopTimerRef timer, void *context)
|
||||
typedef TIMER_CALLBACK(timer_callback);
|
||||
|
||||
#define ALIGN_NONE 0
|
||||
#define ALIGN_LEFT 1
|
||||
#define ALIGN_RIGHT 2
|
||||
#define ALIGN_TOP 3
|
||||
#define ALIGN_BOTTOM 4
|
||||
#define ALIGN_CENTER 5
|
||||
|
||||
struct bar_line
|
||||
{
|
||||
CTLineRef line;
|
||||
CGFloat ascent;
|
||||
CGFloat descent;
|
||||
CGRect bounds;
|
||||
struct rgba_color color;
|
||||
};
|
||||
|
||||
struct bar
|
||||
{
|
||||
uint32_t id;
|
||||
uint32_t did;
|
||||
CGContextRef context;
|
||||
CFRunLoopSourceRef power_source;
|
||||
CFRunLoopTimerRef refresh_timer;
|
||||
CGRect frame;
|
||||
};
|
||||
|
||||
void bar_refresh(struct bar *bar);
|
||||
void bar_resize(struct bar *bar);
|
||||
struct bar *bar_create(uint32_t did);
|
||||
void bar_destroy(struct bar *bar);
|
||||
|
||||
#endif
|
229
src/bar_manager.c
Normal file
229
src/bar_manager.c
Normal file
|
@ -0,0 +1,229 @@
|
|||
#include "bar_manager.h"
|
||||
|
||||
void bar_manager_set_foreground_color(struct bar_manager *bar_manager, uint32_t color)
|
||||
{
|
||||
bar_manager->foreground_color = rgba_color_from_hex(color);
|
||||
if (bar_manager->_space_icon_strip) bar_manager_set_space_strip(bar_manager, bar_manager->_space_icon_strip);
|
||||
if (bar_manager->_power_icon_strip) bar_manager_set_power_strip(bar_manager, bar_manager->_power_icon_strip);
|
||||
if (bar_manager->_clock_icon) bar_manager_set_clock_icon(bar_manager, bar_manager->_clock_icon);
|
||||
if (bar_manager->_space_icon) bar_manager_set_space_icon(bar_manager, bar_manager->_space_icon);
|
||||
bar_manager_refresh(bar_manager);
|
||||
}
|
||||
|
||||
void bar_manager_set_background_color(struct bar_manager *bar_manager, uint32_t color)
|
||||
{
|
||||
bar_manager->background_color = rgba_color_from_hex(color);
|
||||
bar_manager_refresh(bar_manager);
|
||||
}
|
||||
|
||||
void bar_manager_set_text_font(struct bar_manager *bar_manager, char *font_string)
|
||||
{
|
||||
if (bar_manager->t_font) {
|
||||
CFRelease(bar_manager->t_font);
|
||||
}
|
||||
|
||||
if (bar_manager->space_underline.line) {
|
||||
bar_destroy_line(bar_manager->space_underline);
|
||||
}
|
||||
|
||||
if (bar_manager->power_underline.line) {
|
||||
bar_destroy_line(bar_manager->power_underline);
|
||||
}
|
||||
|
||||
if (bar_manager->clock_underline.line) {
|
||||
bar_destroy_line(bar_manager->clock_underline);
|
||||
}
|
||||
|
||||
if (font_string != bar_manager->t_font_prop) {
|
||||
if (bar_manager->t_font_prop) {
|
||||
free(bar_manager->t_font_prop);
|
||||
}
|
||||
|
||||
bar_manager->t_font_prop = font_string;
|
||||
}
|
||||
|
||||
bar_manager->t_font = bar_create_font(bar_manager->t_font_prop);
|
||||
bar_manager->space_underline = bar_prepare_line(bar_manager->t_font, "______", rgba_color_from_hex(0xffd4d232));
|
||||
bar_manager->power_underline = bar_prepare_line(bar_manager->t_font, "__________", rgba_color_from_hex(0xffd75f5f));
|
||||
bar_manager->clock_underline = bar_prepare_line(bar_manager->t_font, "__________", rgba_color_from_hex(0xff458588));
|
||||
bar_manager_refresh(bar_manager);
|
||||
}
|
||||
|
||||
void bar_manager_set_icon_font(struct bar_manager *bar_manager, char *font_string)
|
||||
{
|
||||
if (bar_manager->i_font) {
|
||||
CFRelease(bar_manager->i_font);
|
||||
}
|
||||
|
||||
if (font_string != bar_manager->i_font_prop) {
|
||||
if (bar_manager->i_font_prop) {
|
||||
free(bar_manager->i_font_prop);
|
||||
}
|
||||
|
||||
bar_manager->i_font_prop = font_string;
|
||||
}
|
||||
|
||||
bar_manager->i_font = bar_create_font(bar_manager->i_font_prop);
|
||||
if (bar_manager->_space_icon_strip) bar_manager_set_space_strip(bar_manager, bar_manager->_space_icon_strip);
|
||||
if (bar_manager->_power_icon_strip) bar_manager_set_power_strip(bar_manager, bar_manager->_power_icon_strip);
|
||||
if (bar_manager->_clock_icon) bar_manager_set_clock_icon(bar_manager, bar_manager->_clock_icon);
|
||||
if (bar_manager->_space_icon) bar_manager_set_space_icon(bar_manager, bar_manager->_space_icon);
|
||||
bar_manager_refresh(bar_manager);
|
||||
}
|
||||
|
||||
void bar_manager_set_space_strip(struct bar_manager *bar_manager, char **icon_strip)
|
||||
{
|
||||
for (int i = 0; i < buf_len(bar_manager->space_icon_strip); ++i) {
|
||||
bar_destroy_line(bar_manager->space_icon_strip[i]);
|
||||
}
|
||||
|
||||
buf_free(bar_manager->space_icon_strip);
|
||||
bar_manager->space_icon_strip = NULL;
|
||||
|
||||
if (icon_strip != bar_manager->_space_icon_strip) {
|
||||
for (int i = 0; i < buf_len(bar_manager->_space_icon_strip); ++i) {
|
||||
free(bar_manager->_space_icon_strip[i]);
|
||||
}
|
||||
|
||||
buf_free(bar_manager->_space_icon_strip);
|
||||
bar_manager->_space_icon_strip = icon_strip;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buf_len(bar_manager->_space_icon_strip); ++i) {
|
||||
struct bar_line space_line = bar_prepare_line(bar_manager->i_font, bar_manager->_space_icon_strip[i], bar_manager->foreground_color);
|
||||
buf_push(bar_manager->space_icon_strip, space_line);
|
||||
}
|
||||
|
||||
bar_manager_refresh(bar_manager);
|
||||
}
|
||||
|
||||
void bar_manager_set_power_strip(struct bar_manager *bar_manager, char **icon_strip)
|
||||
{
|
||||
if (bar_manager->battr_icon.line) {
|
||||
bar_destroy_line(bar_manager->battr_icon);
|
||||
}
|
||||
|
||||
if (bar_manager->power_icon.line) {
|
||||
bar_destroy_line(bar_manager->power_icon);
|
||||
}
|
||||
|
||||
if (icon_strip != bar_manager->_power_icon_strip) {
|
||||
for (int i = 0; i < buf_len(bar_manager->_power_icon_strip); ++i) {
|
||||
free(bar_manager->_power_icon_strip[i]);
|
||||
}
|
||||
|
||||
buf_free(bar_manager->_power_icon_strip);
|
||||
bar_manager->_power_icon_strip = icon_strip;
|
||||
}
|
||||
|
||||
if (buf_len(bar_manager->_power_icon_strip) == 2) {
|
||||
bar_manager->battr_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_power_icon_strip[0], rgba_color_from_hex(0xffd75f5f));
|
||||
bar_manager->power_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_power_icon_strip[1], rgba_color_from_hex(0xffcd950c));
|
||||
} else {
|
||||
bar_manager->battr_icon = bar_prepare_line(bar_manager->i_font, "", rgba_color_from_hex(0xffd75f5f));
|
||||
bar_manager->power_icon = bar_prepare_line(bar_manager->i_font, "", rgba_color_from_hex(0xffcd950c));
|
||||
}
|
||||
|
||||
bar_manager_refresh(bar_manager);
|
||||
}
|
||||
|
||||
void bar_manager_set_clock_icon(struct bar_manager *bar_manager, char *icon)
|
||||
{
|
||||
if (bar_manager->clock_icon.line) {
|
||||
bar_destroy_line(bar_manager->clock_icon);
|
||||
}
|
||||
|
||||
if (icon != bar_manager->_clock_icon) {
|
||||
if (bar_manager->_clock_icon) {
|
||||
free(bar_manager->_clock_icon);
|
||||
}
|
||||
|
||||
bar_manager->_clock_icon = icon;
|
||||
}
|
||||
|
||||
bar_manager->clock_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_clock_icon, bar_manager->foreground_color);
|
||||
|
||||
bar_manager_refresh(bar_manager);
|
||||
}
|
||||
|
||||
void bar_manager_set_space_icon(struct bar_manager *bar_manager, char *icon)
|
||||
{
|
||||
if (bar_manager->space_icon.line) {
|
||||
bar_destroy_line(bar_manager->space_icon);
|
||||
}
|
||||
|
||||
if (icon != bar_manager->_space_icon) {
|
||||
if (bar_manager->_space_icon) {
|
||||
free(bar_manager->_space_icon);
|
||||
}
|
||||
|
||||
bar_manager->_space_icon = icon;
|
||||
}
|
||||
|
||||
bar_manager->space_icon = bar_prepare_line(bar_manager->i_font, bar_manager->_space_icon, bar_manager->foreground_color);
|
||||
|
||||
bar_manager_refresh(bar_manager);
|
||||
}
|
||||
|
||||
void bar_manager_add_display(struct bar_manager *bar_manager, uint32_t did)
|
||||
{
|
||||
for (int i = 0; i < bar_manager->bar_count; ++i) {
|
||||
if (bar_manager->bars[i]->did == did)
|
||||
return;
|
||||
}
|
||||
|
||||
bar_manager->bar_count++;
|
||||
bar_manager->bars = realloc(bar_manager->bars, sizeof(struct bar *) * bar_manager->bar_count);
|
||||
bar_manager->bars[bar_manager->bar_count - 1] = bar_create(did);
|
||||
}
|
||||
|
||||
void bar_manager_remove_display(struct bar_manager *bar_manager, uint32_t did)
|
||||
{
|
||||
for (int i = 0; i < bar_manager->bar_count; ++i)
|
||||
{
|
||||
if (bar_manager->bars[i]->did == did) {
|
||||
free (bar_manager->bars[i]);
|
||||
bar_manager->bars[i] = bar_manager->bars[bar_manager->bar_count - 1];
|
||||
bar_manager->bar_count--;
|
||||
bar_manager->bars = realloc(bar_manager->bars, sizeof(struct bar *) * bar_manager->bar_count);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bar_manager_refresh(struct bar_manager *bar_manager)
|
||||
{
|
||||
for (int i = 0; i < bar_manager->bar_count; ++i)
|
||||
bar_refresh(bar_manager->bars[i]);
|
||||
}
|
||||
|
||||
void bar_manager_resize(struct bar_manager *bar_manager)
|
||||
{
|
||||
for (int i = 0; i < bar_manager->bar_count; ++i)
|
||||
bar_resize(bar_manager->bars[i]);
|
||||
}
|
||||
|
||||
void bar_manager_init(struct bar_manager *bar_manager)
|
||||
{
|
||||
bar_manager->bars = NULL;
|
||||
bar_manager->bar_count = 0;
|
||||
bar_manager_set_text_font(bar_manager, string_copy("Helvetica Neue:Regular:10.0"));
|
||||
bar_manager_set_icon_font(bar_manager, string_copy("FontAwesome:Regular:10.0"));
|
||||
bar_manager_set_background_color(bar_manager, 0xff202020);
|
||||
bar_manager_set_foreground_color(bar_manager, 0xffa8a8a8);
|
||||
bar_manager_set_clock_icon(bar_manager, string_copy(" "));
|
||||
bar_manager_set_space_icon(bar_manager, string_copy("*"));
|
||||
bar_manager_set_power_strip(bar_manager, NULL);
|
||||
}
|
||||
|
||||
void bar_manager_begin(struct bar_manager *bar_manager)
|
||||
{
|
||||
bar_manager->bar_count = display_manager_active_display_count();
|
||||
bar_manager->bars = (struct bar **) malloc(sizeof(struct bar *) * bar_manager->bar_count);
|
||||
|
||||
for (uint32_t index=1; index <= bar_manager->bar_count; index++)
|
||||
{
|
||||
uint32_t did = display_manager_arrangement_display_id(index);
|
||||
bar_manager->bars[index - 1] = bar_create(did);
|
||||
}
|
||||
}
|
46
src/bar_manager.h
Normal file
46
src/bar_manager.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
#ifndef BAR_MANAGER_H
|
||||
#define BAR_MANAGER_H
|
||||
|
||||
struct bar_manager
|
||||
{
|
||||
struct bar **bars;
|
||||
int bar_count;
|
||||
char *t_font_prop;
|
||||
char *i_font_prop;
|
||||
CTFontRef t_font;
|
||||
CTFontRef i_font;
|
||||
char **_space_icon_strip;
|
||||
char **_power_icon_strip;
|
||||
char *_clock_icon;
|
||||
char *_space_icon;
|
||||
struct rgba_color foreground_color;
|
||||
struct rgba_color background_color;
|
||||
struct rgba_color background_color_dim;
|
||||
struct bar_line *space_icon_strip;
|
||||
struct bar_line space_icon;
|
||||
struct bar_line clock_icon;
|
||||
struct bar_line battr_icon;
|
||||
struct bar_line power_icon;
|
||||
struct bar_line space_underline;
|
||||
struct bar_line power_underline;
|
||||
struct bar_line clock_underline;
|
||||
};
|
||||
|
||||
void bar_manager_set_foreground_color(struct bar_manager *bar_manager, uint32_t color);
|
||||
void bar_manager_set_background_color(struct bar_manager *bar_manager, uint32_t color);
|
||||
void bar_manager_set_text_font(struct bar_manager *bar_manager, char *font_string);
|
||||
void bar_manager_set_icon_font(struct bar_manager *bar_manager, char *font_string);
|
||||
void bar_manager_set_space_strip(struct bar_manager *bar_manager, char **icon_strip);
|
||||
void bar_manager_set_power_strip(struct bar_manager *bar_manager, char **icon_strip);
|
||||
void bar_manager_set_clock_icon(struct bar_manager *bar_manager, char *icon);
|
||||
void bar_manager_set_space_icon(struct bar_manager *bar_manager, char *icon);
|
||||
|
||||
void bar_manager_add_display(struct bar_manager *bar_manager, uint32_t did);
|
||||
void bar_manager_remove_display(struct bar_manager *bar_manager, uint32_t did);
|
||||
void bar_manager_refresh(struct bar_manager *bar_manager);
|
||||
void bar_manager_resize(struct bar_manager *bar_manager);
|
||||
void bar_manager_begin(struct bar_manager *bar_manager);
|
||||
void bar_manager_init(struct bar_manager *bar_manager);
|
||||
|
||||
|
||||
#endif
|
106
src/display.c
Normal file
106
src/display.c
Normal file
|
@ -0,0 +1,106 @@
|
|||
#include "display.h"
|
||||
|
||||
extern struct event_loop g_event_loop;
|
||||
extern struct bar g_bar;
|
||||
extern int g_connection;
|
||||
|
||||
static DISPLAY_EVENT_HANDLER(display_handler)
|
||||
{
|
||||
if (flags & kCGDisplayAddFlag) {
|
||||
struct event *event = event_create(&g_event_loop, DISPLAY_ADDED, (void *)(intptr_t) did);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
} else if (flags & kCGDisplayRemoveFlag) {
|
||||
struct event *event = event_create(&g_event_loop, DISPLAY_REMOVED, (void *)(intptr_t) did);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
} else if (flags & kCGDisplayMovedFlag) {
|
||||
struct event *event = event_create(&g_event_loop, DISPLAY_MOVED, (void *)(intptr_t) did);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
} else if (flags & kCGDisplayDesktopShapeChangedFlag) {
|
||||
struct event *event = event_create(&g_event_loop, DISPLAY_RESIZED, (void *)(intptr_t) did);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
}
|
||||
}
|
||||
|
||||
CFStringRef display_uuid(uint32_t did)
|
||||
{
|
||||
CFUUIDRef uuid_ref = CGDisplayCreateUUIDFromDisplayID(did);
|
||||
if (!uuid_ref) return NULL;
|
||||
|
||||
CFStringRef uuid_str = CFUUIDCreateString(NULL, uuid_ref);
|
||||
CFRelease(uuid_ref);
|
||||
|
||||
return uuid_str;
|
||||
}
|
||||
|
||||
CGRect display_bounds(uint32_t did)
|
||||
{
|
||||
return CGDisplayBounds(did);
|
||||
}
|
||||
|
||||
uint64_t display_space_id(uint32_t did)
|
||||
{
|
||||
CFStringRef uuid = display_uuid(did);
|
||||
if (!uuid) return 0;
|
||||
|
||||
uint64_t sid = SLSManagedDisplayGetCurrentSpace(g_connection, uuid);
|
||||
CFRelease(uuid);
|
||||
return sid;
|
||||
}
|
||||
|
||||
uint64_t *display_space_list(uint32_t did, int *count)
|
||||
{
|
||||
CFStringRef uuid = display_uuid(did);
|
||||
if (!uuid) return NULL;
|
||||
|
||||
CFArrayRef display_spaces_ref = SLSCopyManagedDisplaySpaces(g_connection);
|
||||
if (!display_spaces_ref) return NULL;
|
||||
|
||||
uint64_t *space_list = NULL;
|
||||
int display_spaces_count = CFArrayGetCount(display_spaces_ref);
|
||||
|
||||
for (int i = 0; i < display_spaces_count; ++i) {
|
||||
CFDictionaryRef display_ref = CFArrayGetValueAtIndex(display_spaces_ref, i);
|
||||
CFStringRef identifier = CFDictionaryGetValue(display_ref, CFSTR("Display Identifier"));
|
||||
if (!CFEqual(uuid, identifier)) continue;
|
||||
|
||||
CFArrayRef spaces_ref = CFDictionaryGetValue(display_ref, CFSTR("Spaces"));
|
||||
int spaces_count = CFArrayGetCount(spaces_ref);
|
||||
|
||||
space_list = malloc(sizeof(uint64_t) * spaces_count);
|
||||
*count = spaces_count;
|
||||
|
||||
for (int j = 0; j < spaces_count; ++j) {
|
||||
CFDictionaryRef space_ref = CFArrayGetValueAtIndex(spaces_ref, j);
|
||||
CFNumberRef sid_ref = CFDictionaryGetValue(space_ref, CFSTR("id64"));
|
||||
CFNumberGetValue(sid_ref, CFNumberGetType(sid_ref), &space_list[j]);
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease(display_spaces_ref);
|
||||
CFRelease(uuid);
|
||||
|
||||
return space_list;
|
||||
}
|
||||
|
||||
int display_arrangement(uint32_t did)
|
||||
{
|
||||
CFStringRef uuid = display_uuid(did);
|
||||
if (!uuid) return 0;
|
||||
|
||||
CFArrayRef displays = SLSCopyManagedDisplays(g_connection);
|
||||
if (!displays) return 0;
|
||||
|
||||
int result = 0;
|
||||
int displays_count = CFArrayGetCount(displays);
|
||||
|
||||
for (int i = 0; i < displays_count; ++i) {
|
||||
if (CFEqual(CFArrayGetValueAtIndex(displays, i), uuid)) {
|
||||
result = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease(displays);
|
||||
CFRelease(uuid);
|
||||
return result;
|
||||
}
|
24
src/display.h
Normal file
24
src/display.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#ifndef DISPLAY_H
|
||||
#define DISPLAY_H
|
||||
|
||||
extern int SLSGetSpaceManagementMode(int cid);
|
||||
extern CFArrayRef SLSCopyManagedDisplaySpaces(int cid);
|
||||
extern CGError SLSProcessAssignToSpace(int cid, pid_t pid, uint64_t sid);
|
||||
extern CGError SLSProcessAssignToAllSpaces(int cid, pid_t pid);
|
||||
extern void SLSMoveWindowsToManagedSpace(int cid, CFArrayRef window_list, uint64_t sid);
|
||||
extern CGError CoreDockSendNotification(CFStringRef notification, int unknown);
|
||||
|
||||
#define DISPLAY_EVENT_HANDLER(name) void name(uint32_t did, CGDisplayChangeSummaryFlags flags, void *context)
|
||||
typedef DISPLAY_EVENT_HANDLER(display_callback);
|
||||
|
||||
extern CFUUIDRef CGDisplayCreateUUIDFromDisplayID(uint32_t did);
|
||||
extern CFArrayRef SLSCopyManagedDisplays(int cid);
|
||||
extern uint64_t SLSManagedDisplayGetCurrentSpace(int cid, CFStringRef uuid);
|
||||
|
||||
CFStringRef display_uuid(uint32_t did);
|
||||
CGRect display_bounds(uint32_t did);
|
||||
uint64_t display_space_id(uint32_t did);
|
||||
uint64_t *display_space_list(uint32_t did, int *count);
|
||||
int display_arrangement(uint32_t did);
|
||||
|
||||
#endif
|
187
src/display_manager.c
Normal file
187
src/display_manager.c
Normal file
|
@ -0,0 +1,187 @@
|
|||
#include "display_manager.h"
|
||||
|
||||
extern struct window_manager g_window_manager;
|
||||
extern int g_connection;
|
||||
|
||||
uint32_t display_manager_main_display_id(void)
|
||||
{
|
||||
return CGMainDisplayID();
|
||||
}
|
||||
|
||||
CFStringRef display_manager_active_display_uuid(void)
|
||||
{
|
||||
return SLSCopyActiveMenuBarDisplayIdentifier(g_connection);
|
||||
}
|
||||
|
||||
uint32_t display_manager_active_display_id(void)
|
||||
{
|
||||
uint32_t result = 0;
|
||||
CFStringRef uuid = display_manager_active_display_uuid();
|
||||
CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, uuid);
|
||||
result = CGDisplayGetDisplayIDFromUUID(uuid_ref);
|
||||
CFRelease(uuid_ref);
|
||||
CFRelease(uuid);
|
||||
return result;
|
||||
}
|
||||
|
||||
CFStringRef display_manager_dock_display_uuid(void)
|
||||
{
|
||||
CGRect dock = display_manager_dock_rect();
|
||||
return SLSCopyBestManagedDisplayForRect(g_connection, dock);
|
||||
}
|
||||
|
||||
uint32_t display_manager_dock_display_id(void)
|
||||
{
|
||||
CFStringRef uuid = display_manager_dock_display_uuid();
|
||||
if (!uuid) return 0;
|
||||
|
||||
CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, uuid);
|
||||
uint32_t result = CGDisplayGetDisplayIDFromUUID(uuid_ref);
|
||||
CFRelease(uuid_ref);
|
||||
CFRelease(uuid);
|
||||
return result;
|
||||
}
|
||||
|
||||
CFStringRef display_manager_cursor_display_uuid(void)
|
||||
{
|
||||
CGPoint cursor;
|
||||
SLSGetCurrentCursorLocation(g_connection, &cursor);
|
||||
return SLSCopyBestManagedDisplayForPoint(g_connection, cursor);
|
||||
}
|
||||
|
||||
uint32_t display_manager_cursor_display_id(void)
|
||||
{
|
||||
CFStringRef uuid = display_manager_cursor_display_uuid();
|
||||
if (!uuid) return 0;
|
||||
|
||||
CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, uuid);
|
||||
uint32_t result = CGDisplayGetDisplayIDFromUUID(uuid_ref);
|
||||
CFRelease(uuid_ref);
|
||||
CFRelease(uuid);
|
||||
return result;
|
||||
}
|
||||
|
||||
CFStringRef display_manager_arrangement_display_uuid(int arrangement)
|
||||
{
|
||||
CFStringRef result = NULL;
|
||||
CFArrayRef displays = SLSCopyManagedDisplays(g_connection);
|
||||
|
||||
int displays_count = CFArrayGetCount(displays);
|
||||
for (int i = 0; i < displays_count; ++i) {
|
||||
if ((i+1) != arrangement) continue;
|
||||
result = CFRetain(CFArrayGetValueAtIndex(displays, i));
|
||||
break;
|
||||
}
|
||||
|
||||
CFRelease(displays);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t display_manager_arrangement_display_id(int arrangement)
|
||||
{
|
||||
uint32_t result = 0;
|
||||
CFArrayRef displays = SLSCopyManagedDisplays(g_connection);
|
||||
|
||||
int displays_count = CFArrayGetCount(displays);
|
||||
for (int i = 0; i < displays_count; ++i) {
|
||||
if ((i+1) != arrangement) continue;
|
||||
CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, CFArrayGetValueAtIndex(displays, i));
|
||||
result = CGDisplayGetDisplayIDFromUUID(uuid_ref);
|
||||
CFRelease(uuid_ref);
|
||||
break;
|
||||
}
|
||||
|
||||
CFRelease(displays);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t display_manager_first_display_id(void)
|
||||
{
|
||||
return display_manager_arrangement_display_id(1);
|
||||
}
|
||||
|
||||
uint32_t display_manager_last_display_id(void)
|
||||
{
|
||||
int arrangement = display_manager_active_display_count();
|
||||
return display_manager_arrangement_display_id(arrangement);
|
||||
}
|
||||
|
||||
bool display_manager_menu_bar_hidden(void)
|
||||
{
|
||||
int status = 0;
|
||||
SLSGetMenuBarAutohideEnabled(g_connection, &status);
|
||||
return status;
|
||||
}
|
||||
|
||||
CGRect display_manager_menu_bar_rect(uint32_t did)
|
||||
{
|
||||
CGRect bounds = {};
|
||||
SLSGetRevealedMenuBarBounds(&bounds, g_connection, display_space_id(did));
|
||||
return bounds;
|
||||
}
|
||||
|
||||
bool display_manager_dock_hidden(void)
|
||||
{
|
||||
return CoreDockGetAutoHideEnabled();
|
||||
}
|
||||
|
||||
int display_manager_dock_orientation(void)
|
||||
{
|
||||
int pinning = 0;
|
||||
int orientation = 0;
|
||||
CoreDockGetOrientationAndPinning(&orientation, &pinning);
|
||||
return orientation;
|
||||
}
|
||||
|
||||
CGRect display_manager_dock_rect(void)
|
||||
{
|
||||
int reason = 0;
|
||||
CGRect bounds = {};
|
||||
SLSGetDockRectWithReason(g_connection, &bounds, &reason);
|
||||
return bounds;
|
||||
}
|
||||
|
||||
bool display_manager_active_display_is_animating(void)
|
||||
{
|
||||
CFStringRef uuid = display_manager_active_display_uuid();
|
||||
bool result = SLSManagedDisplayIsAnimating(g_connection, uuid);
|
||||
CFRelease(uuid);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool display_manager_display_is_animating(uint32_t did)
|
||||
{
|
||||
CFStringRef uuid = display_uuid(did);
|
||||
if (!uuid) return false;
|
||||
|
||||
bool result = SLSManagedDisplayIsAnimating(g_connection, uuid);
|
||||
CFRelease(uuid);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t display_manager_active_display_count(void)
|
||||
{
|
||||
uint32_t count;
|
||||
CGGetActiveDisplayList(0, NULL, &count);
|
||||
return count;
|
||||
}
|
||||
|
||||
uint32_t *display_manager_active_display_list(uint32_t *count)
|
||||
{
|
||||
int display_count = display_manager_active_display_count();
|
||||
uint32_t *result = malloc(sizeof(uint32_t) * display_count);
|
||||
CGGetActiveDisplayList(display_count, result, count);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool display_manager_begin(struct display_manager *dm)
|
||||
{
|
||||
dm->current_display_id = display_manager_active_display_id();
|
||||
dm->last_display_id = dm->current_display_id;
|
||||
return CGDisplayRegisterReconfigurationCallback(display_handler, NULL) == kCGErrorSuccess;
|
||||
}
|
||||
|
||||
bool display_manager_end(void)
|
||||
{
|
||||
return CGDisplayRemoveReconfigurationCallback(display_handler, NULL) == kCGErrorSuccess;
|
||||
}
|
49
src/display_manager.h
Normal file
49
src/display_manager.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
#ifndef DISPLAY_MANAGER_H
|
||||
#define DISPLAY_MANAGER_H
|
||||
|
||||
extern CFStringRef SLSCopyActiveMenuBarDisplayIdentifier(int cid);
|
||||
extern CFStringRef SLSCopyBestManagedDisplayForPoint(int cid, CGPoint point);
|
||||
extern bool SLSManagedDisplayIsAnimating(int cid, CFStringRef uuid);
|
||||
extern CGError SLSGetMenuBarAutohideEnabled(int cid, int *enabled);
|
||||
extern CGError SLSGetRevealedMenuBarBounds(CGRect *rect, int cid, uint64_t sid);
|
||||
extern CGError SLSGetDockRectWithReason(int cid, CGRect *rect, int *reason);
|
||||
extern Boolean CoreDockGetAutoHideEnabled(void);
|
||||
extern void CoreDockGetOrientationAndPinning(int *orientation, int *pinning);
|
||||
|
||||
#define DOCK_ORIENTATION_BOTTOM 2
|
||||
#define DOCK_ORIENTATION_LEFT 3
|
||||
#define DOCK_ORIENTATION_RIGHT 4
|
||||
|
||||
struct display_manager
|
||||
{
|
||||
uint32_t current_display_id;
|
||||
uint32_t last_display_id;
|
||||
};
|
||||
|
||||
uint32_t display_manager_main_display_id(void);
|
||||
CFStringRef display_manager_active_display_uuid(void);
|
||||
uint32_t display_manager_active_display_id(void);
|
||||
CFStringRef display_manager_dock_display_uuid(void);
|
||||
uint32_t display_manager_dock_display_id(void);
|
||||
CFStringRef display_manager_cursor_display_uuid(void);
|
||||
uint32_t display_manager_cursor_display_id(void);
|
||||
CFStringRef display_manager_arrangement_display_uuid(int arrangement);
|
||||
uint32_t display_manager_arrangement_display_id(int arrangement);
|
||||
uint32_t display_manager_prev_display_id(uint32_t did);
|
||||
uint32_t display_manager_next_display_id(uint32_t did);
|
||||
uint32_t display_manager_first_display_id(void);
|
||||
uint32_t display_manager_last_display_id(void);
|
||||
bool display_manager_menu_bar_hidden(void);
|
||||
CGRect display_manager_menu_bar_rect(uint32_t did);
|
||||
bool display_manager_dock_hidden(void);
|
||||
int display_manager_dock_orientation(void);
|
||||
CGRect display_manager_dock_rect(void);
|
||||
bool display_manager_active_display_is_animating(void);
|
||||
bool display_manager_display_is_animating(uint32_t did);
|
||||
uint32_t display_manager_active_display_count(void);
|
||||
uint32_t *display_manager_active_display_list(uint32_t *count);
|
||||
//void display_manager_focus_display(uint32_t did);
|
||||
bool display_manager_begin(struct display_manager *dm);
|
||||
bool display_manager_end(void);
|
||||
|
||||
#endif
|
237
src/event.c
Normal file
237
src/event.c
Normal file
|
@ -0,0 +1,237 @@
|
|||
#include "event.h"
|
||||
|
||||
extern struct event_loop g_event_loop;
|
||||
extern struct process_manager g_process_manager;
|
||||
extern struct display_manager g_display_manager;
|
||||
extern struct bar_manager g_bar_manager;
|
||||
extern struct application_manager g_application_manager;
|
||||
extern bool g_mission_control_active;
|
||||
extern int g_connection;
|
||||
|
||||
enum event_type event_type_from_string(const char *str)
|
||||
{
|
||||
for (int i = EVENT_TYPE_UNKNOWN + 1; i < EVENT_TYPE_COUNT; ++i) {
|
||||
if (string_equals(str, event_type_str[i])) return i;
|
||||
}
|
||||
|
||||
return EVENT_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
struct event *event_create(struct event_loop *event_loop, enum event_type type, void *context)
|
||||
{
|
||||
struct event *event = memory_pool_push(&event_loop->pool, struct event);
|
||||
event->type = type;
|
||||
event->context = context;
|
||||
event->param1 = 0;
|
||||
event->info = 0;
|
||||
#ifdef DEBUG
|
||||
uint64_t count = __sync_add_and_fetch(&event_loop->count, 1);
|
||||
assert(count > 0 && count < EVENT_MAX_COUNT);
|
||||
#endif
|
||||
return event;
|
||||
}
|
||||
|
||||
struct event *event_create_p1(struct event_loop *event_loop, enum event_type type, void *context, int param1)
|
||||
{
|
||||
struct event *event = memory_pool_push(&event_loop->pool, struct event);
|
||||
event->type = type;
|
||||
event->context = context;
|
||||
event->param1 = param1;
|
||||
event->info = 0;
|
||||
#ifdef DEBUG
|
||||
uint64_t count = __sync_add_and_fetch(&event_loop->count, 1);
|
||||
assert(count > 0 && count < EVENT_MAX_COUNT);
|
||||
#endif
|
||||
return event;
|
||||
}
|
||||
|
||||
void event_destroy(struct event_loop *event_loop, struct event *event)
|
||||
{
|
||||
switch (event->type) {
|
||||
default: break;
|
||||
case APPLICATION_TERMINATED: {
|
||||
process_destroy(event->context);
|
||||
} break;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
uint64_t count = __sync_sub_and_fetch(&event_loop->count, 1);
|
||||
assert(count >= 0 && count < EVENT_MAX_COUNT);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_LAUNCHED)
|
||||
{
|
||||
struct process *process = context;
|
||||
debug("%s: %s\n", __FUNCTION__, process->name);
|
||||
|
||||
if ((process->terminated) || (kill(process->pid, 0) == -1)) {
|
||||
debug("%s: %s terminated during launch\n", __FUNCTION__, process->name);
|
||||
return EVENT_FAILURE;
|
||||
}
|
||||
|
||||
struct application *application = application_create(process);
|
||||
if (application_observe(application)) {
|
||||
application_manager_add_application(&g_application_manager, application);
|
||||
|
||||
return EVENT_SUCCESS;
|
||||
} else {
|
||||
bool retry_ax = application->retry;
|
||||
application_unobserve(application);
|
||||
application_destroy(application);
|
||||
debug("%s: could not observe %s (%d)\n", __FUNCTION__, process->name, retry_ax);
|
||||
|
||||
if (retry_ax) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.01f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
struct event *event = event_create(&g_event_loop, APPLICATION_LAUNCHED, process);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
});
|
||||
}
|
||||
|
||||
return EVENT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_TERMINATED)
|
||||
{
|
||||
struct process *process = context;
|
||||
struct application *application = application_manager_find_application(&g_application_manager, process->pid);
|
||||
|
||||
if (!application) {
|
||||
debug("%s: %s (not observed)\n", __FUNCTION__, process->name);
|
||||
return EVENT_FAILURE;
|
||||
}
|
||||
|
||||
debug("%s: %s\n", __FUNCTION__, process->name);
|
||||
application_manager_remove_application(&g_application_manager, application->pid);
|
||||
|
||||
application_unobserve(application);
|
||||
application_destroy(application);
|
||||
|
||||
return EVENT_SUCCESS;
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_FRONT_SWITCHED)
|
||||
{
|
||||
debug("%s\n", __FUNCTION__);
|
||||
bar_manager_refresh(&g_bar_manager);
|
||||
|
||||
return EVENT_SUCCESS;
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_WINDOW_FOCUSED)
|
||||
{
|
||||
debug("%s\n", __FUNCTION__);
|
||||
bar_manager_refresh(&g_bar_manager);
|
||||
|
||||
return EVENT_SUCCESS;
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_WINDOW_TITLE_CHANGED)
|
||||
{
|
||||
debug("%s\n", __FUNCTION__);
|
||||
|
||||
// TODO: we can optimize by checking if it the focused window
|
||||
bar_manager_refresh(&g_bar_manager);
|
||||
|
||||
return EVENT_SUCCESS;
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_SPACE_CHANGED)
|
||||
{
|
||||
debug("%s\n", __FUNCTION__);
|
||||
|
||||
bar_manager_refresh(&g_bar_manager);
|
||||
|
||||
return EVENT_SUCCESS;
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_CHANGED)
|
||||
{
|
||||
g_display_manager.last_display_id = g_display_manager.current_display_id;
|
||||
g_display_manager.current_display_id = display_manager_active_display_id();
|
||||
|
||||
debug("%s: %d\n", __FUNCTION__, g_display_manager.current_display_id);
|
||||
|
||||
bar_manager_refresh(&g_bar_manager);
|
||||
|
||||
return EVENT_SUCCESS;
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_ADDED)
|
||||
{
|
||||
uint32_t did = (uint32_t)(intptr_t) context;
|
||||
debug("%s: %d\n", __FUNCTION__, did);
|
||||
bar_manager_add_display(&g_bar_manager, did);
|
||||
return EVENT_SUCCESS;
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_REMOVED)
|
||||
{
|
||||
uint32_t did = (uint32_t)(intptr_t) context;
|
||||
debug("%s: %d\n", __FUNCTION__, did);
|
||||
bar_manager_remove_display(&g_bar_manager, did);
|
||||
return EVENT_SUCCESS;
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_MOVED)
|
||||
{
|
||||
uint32_t did = (uint32_t)(intptr_t) context;
|
||||
debug("%s: %d\n", __FUNCTION__, did);
|
||||
bar_manager_resize(&g_bar_manager);
|
||||
return EVENT_SUCCESS;
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_RESIZED)
|
||||
{
|
||||
uint32_t did = (uint32_t)(intptr_t) context;
|
||||
debug("%s: %d\n", __FUNCTION__, did);
|
||||
bar_manager_resize(&g_bar_manager);
|
||||
return EVENT_SUCCESS;
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_MENU_BAR_HIDDEN_CHANGED)
|
||||
{
|
||||
debug("%s:\n", __FUNCTION__);
|
||||
bar_manager_resize(&g_bar_manager);
|
||||
return EVENT_SUCCESS;
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_SYSTEM_WOKE)
|
||||
{
|
||||
debug("%s:\n", __FUNCTION__);
|
||||
bar_manager_refresh(&g_bar_manager);
|
||||
return EVENT_SUCCESS;
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_BAR_REFRESH)
|
||||
{
|
||||
bar_manager_refresh(&g_bar_manager);
|
||||
return EVENT_SUCCESS;
|
||||
}
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_DAEMON_MESSAGE)
|
||||
{
|
||||
FILE *rsp = fdopen(param1, "w");
|
||||
if (!rsp) goto out;
|
||||
|
||||
if (g_verbose) {
|
||||
fprintf(stdout, "%s:", __FUNCTION__);
|
||||
for (char *message = context; *message;) {
|
||||
message += fprintf(stdout, " %s", message);
|
||||
}
|
||||
putc('\n', stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
handle_message(rsp, context);
|
||||
fflush(rsp);
|
||||
fclose(rsp);
|
||||
|
||||
out:
|
||||
socket_close(param1);
|
||||
free(context);
|
||||
|
||||
return EVENT_SUCCESS;
|
||||
}
|
110
src/event.h
Normal file
110
src/event.h
Normal file
|
@ -0,0 +1,110 @@
|
|||
#ifndef EVENT_LOOP_EVENT_H
|
||||
#define EVENT_LOOP_EVENT_H
|
||||
|
||||
#define EVENT_CALLBACK(name) uint32_t name(void *context, int param1)
|
||||
typedef EVENT_CALLBACK(event_callback);
|
||||
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_LAUNCHED);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_TERMINATED);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_APPLICATION_FRONT_SWITCHED);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_WINDOW_FOCUSED);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_WINDOW_TITLE_CHANGED);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_SPACE_CHANGED);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_ADDED);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_REMOVED);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_MOVED);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_RESIZED);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_DISPLAY_CHANGED);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_MENU_BAR_HIDDEN_CHANGED);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_SYSTEM_WOKE);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_BAR_REFRESH);
|
||||
static EVENT_CALLBACK(EVENT_HANDLER_DAEMON_MESSAGE);
|
||||
|
||||
#define EVENT_QUEUED 0x0
|
||||
#define EVENT_PROCESSED 0x1
|
||||
|
||||
#define EVENT_SUCCESS 0x0
|
||||
#define EVENT_FAILURE 0x1
|
||||
#define EVENT_MOUSE_IGNORE 0x2
|
||||
|
||||
#define event_status(e) ((e) & 0x1)
|
||||
#define event_result(e) ((e) >> 0x1)
|
||||
|
||||
enum event_type
|
||||
{
|
||||
EVENT_TYPE_UNKNOWN,
|
||||
APPLICATION_LAUNCHED,
|
||||
APPLICATION_TERMINATED,
|
||||
APPLICATION_FRONT_SWITCHED,
|
||||
WINDOW_FOCUSED,
|
||||
WINDOW_TITLE_CHANGED,
|
||||
SPACE_CHANGED,
|
||||
DISPLAY_ADDED,
|
||||
DISPLAY_REMOVED,
|
||||
DISPLAY_MOVED,
|
||||
DISPLAY_RESIZED,
|
||||
DISPLAY_CHANGED,
|
||||
MENU_BAR_HIDDEN_CHANGED,
|
||||
SYSTEM_WOKE,
|
||||
BAR_REFRESH,
|
||||
DAEMON_MESSAGE,
|
||||
|
||||
EVENT_TYPE_COUNT
|
||||
};
|
||||
|
||||
static const char *event_type_str[] =
|
||||
{
|
||||
[EVENT_TYPE_UNKNOWN] = "event_type_unknown",
|
||||
|
||||
[APPLICATION_LAUNCHED] = "application_launched",
|
||||
[APPLICATION_TERMINATED] = "application_terminated",
|
||||
[APPLICATION_FRONT_SWITCHED] = "application_front_switched",
|
||||
[WINDOW_FOCUSED] = "window_focused",
|
||||
[WINDOW_TITLE_CHANGED] = "window_title_changed",
|
||||
[SPACE_CHANGED] = "space_changed",
|
||||
[DISPLAY_ADDED] = "display_added",
|
||||
[DISPLAY_REMOVED] = "display_removed",
|
||||
[DISPLAY_MOVED] = "display_moved",
|
||||
[DISPLAY_RESIZED] = "display_resized",
|
||||
[DISPLAY_CHANGED] = "display_changed",
|
||||
[MENU_BAR_HIDDEN_CHANGED] = "menu_bar_hidden_changed",
|
||||
[SYSTEM_WOKE] = "system_woke",
|
||||
[BAR_REFRESH] = "bar_refresh",
|
||||
[DAEMON_MESSAGE] = "daemon_message",
|
||||
|
||||
[EVENT_TYPE_COUNT] = "event_type_count"
|
||||
};
|
||||
|
||||
static event_callback *event_handler[] =
|
||||
{
|
||||
[APPLICATION_LAUNCHED] = EVENT_HANDLER_APPLICATION_LAUNCHED,
|
||||
[APPLICATION_TERMINATED] = EVENT_HANDLER_APPLICATION_TERMINATED,
|
||||
[APPLICATION_FRONT_SWITCHED] = EVENT_HANDLER_APPLICATION_FRONT_SWITCHED,
|
||||
[WINDOW_FOCUSED] = EVENT_HANDLER_WINDOW_FOCUSED,
|
||||
[WINDOW_TITLE_CHANGED] = EVENT_HANDLER_WINDOW_TITLE_CHANGED,
|
||||
[SPACE_CHANGED] = EVENT_HANDLER_SPACE_CHANGED,
|
||||
[DISPLAY_ADDED] = EVENT_HANDLER_DISPLAY_ADDED,
|
||||
[DISPLAY_REMOVED] = EVENT_HANDLER_DISPLAY_REMOVED,
|
||||
[DISPLAY_MOVED] = EVENT_HANDLER_DISPLAY_MOVED,
|
||||
[DISPLAY_RESIZED] = EVENT_HANDLER_DISPLAY_RESIZED,
|
||||
[DISPLAY_CHANGED] = EVENT_HANDLER_DISPLAY_CHANGED,
|
||||
[MENU_BAR_HIDDEN_CHANGED] = EVENT_HANDLER_MENU_BAR_HIDDEN_CHANGED,
|
||||
[SYSTEM_WOKE] = EVENT_HANDLER_SYSTEM_WOKE,
|
||||
[BAR_REFRESH] = EVENT_HANDLER_BAR_REFRESH,
|
||||
[DAEMON_MESSAGE] = EVENT_HANDLER_DAEMON_MESSAGE,
|
||||
};
|
||||
|
||||
struct event
|
||||
{
|
||||
void *context;
|
||||
volatile uint32_t *info;
|
||||
enum event_type type;
|
||||
int param1;
|
||||
};
|
||||
|
||||
struct event *event_create(struct event_loop *event_loop, enum event_type type, void *context);
|
||||
struct event *event_create_p1(struct event_loop *event_loop, enum event_type type, void *context, int param1);
|
||||
void event_destroy(struct event_loop *event_loop, struct event *event);
|
||||
enum event_type event_type_from_string(const char *str);
|
||||
|
||||
#endif
|
154
src/event_loop.c
Normal file
154
src/event_loop.c
Normal file
|
@ -0,0 +1,154 @@
|
|||
#include "event_loop.h"
|
||||
|
||||
#ifdef STATS
|
||||
struct cycle_counter
|
||||
{
|
||||
uint64_t cycle_count;
|
||||
uint64_t hit_count;
|
||||
};
|
||||
|
||||
static struct cycle_counter queue_counters[2];
|
||||
static struct cycle_counter event_counters[EVENT_TYPE_COUNT];
|
||||
|
||||
static inline void cycle_counter_tick(const char *name, struct cycle_counter *counter, uint64_t elapsed_cycles)
|
||||
{
|
||||
uint64_t cycle_count = __sync_add_and_fetch(&counter->cycle_count, elapsed_cycles);
|
||||
uint64_t hit_count = __sync_add_and_fetch(&counter->hit_count, 1);
|
||||
fprintf(stdout, "%30s: hits %'25lld | cur %'25lld | avg %'25lld\n",
|
||||
name, hit_count, elapsed_cycles, cycle_count / hit_count)
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool queue_init(struct queue *queue)
|
||||
{
|
||||
if (!memory_pool_init(&queue->pool, QUEUE_POOL_SIZE)) return false;
|
||||
queue->head = memory_pool_push(&queue->pool, struct queue_item);
|
||||
queue->head->data = NULL;
|
||||
queue->head->next = NULL;
|
||||
queue->tail = queue->head;
|
||||
#ifdef DEBUG
|
||||
queue->count = 0;
|
||||
#endif
|
||||
return true;
|
||||
};
|
||||
|
||||
static void queue_push(struct queue *queue, struct event *event)
|
||||
{
|
||||
bool success;
|
||||
struct queue_item *tail, *new_tail;
|
||||
|
||||
#ifdef STATS
|
||||
uint64_t begin_cycles = __rdtsc();
|
||||
#endif
|
||||
|
||||
new_tail = memory_pool_push(&queue->pool, struct queue_item);
|
||||
new_tail->data = event;
|
||||
new_tail->next = NULL;
|
||||
__asm__ __volatile__ ("" ::: "memory");
|
||||
|
||||
do {
|
||||
tail = queue->tail;
|
||||
success = __sync_bool_compare_and_swap(&tail->next, NULL, new_tail);
|
||||
if (!success) __sync_bool_compare_and_swap(&queue->tail, tail, tail->next);
|
||||
} while (!success);
|
||||
__sync_bool_compare_and_swap(&queue->tail, tail, new_tail);
|
||||
|
||||
#ifdef DEBUG
|
||||
uint64_t count = __sync_add_and_fetch(&queue->count, 1);
|
||||
assert(count > 0 && count < QUEUE_MAX_COUNT);
|
||||
#endif
|
||||
|
||||
#ifdef STATS
|
||||
cycle_counter_tick(__FUNCTION__, &queue_counters[0], __rdtsc() - begin_cycles);
|
||||
#endif
|
||||
}
|
||||
|
||||
static struct event *queue_pop(struct queue *queue)
|
||||
{
|
||||
struct queue_item *head;
|
||||
|
||||
#ifdef STATS
|
||||
uint64_t begin_cycles = __rdtsc();
|
||||
#endif
|
||||
|
||||
do {
|
||||
head = queue->head;
|
||||
if (!head->next) return NULL;
|
||||
} while (!__sync_bool_compare_and_swap(&queue->head, head, head->next));
|
||||
|
||||
#ifdef DEBUG
|
||||
uint64_t count = __sync_sub_and_fetch(&queue->count, 1);
|
||||
assert(count >= 0 && count < QUEUE_MAX_COUNT);
|
||||
#endif
|
||||
|
||||
#ifdef STATS
|
||||
cycle_counter_tick(__FUNCTION__, &queue_counters[1], __rdtsc() - begin_cycles);
|
||||
#endif
|
||||
|
||||
return head->next->data;
|
||||
}
|
||||
|
||||
static void *event_loop_run(void *context)
|
||||
{
|
||||
struct event_loop *event_loop = (struct event_loop *) context;
|
||||
struct queue *queue = (struct queue *) &event_loop->queue;
|
||||
|
||||
while (event_loop->is_running) {
|
||||
struct event *event = queue_pop(queue);
|
||||
if (event) {
|
||||
#ifdef STATS
|
||||
uint64_t begin_cycles = __rdtsc();
|
||||
#endif
|
||||
uint32_t result = event_handler[event->type](event->context, event->param1);
|
||||
#ifdef STATS
|
||||
cycle_counter_tick(event_type_str[event->type], &event_counters[event->type], __rdtsc() - begin_cycles);
|
||||
#endif
|
||||
if (event->info) *event->info = (result << 0x1) | EVENT_PROCESSED;
|
||||
|
||||
event_destroy(event_loop, event);
|
||||
} else {
|
||||
sem_wait(event_loop->semaphore);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void event_loop_post(struct event_loop *event_loop, struct event *event)
|
||||
{
|
||||
assert(event_loop->is_running);
|
||||
queue_push(&event_loop->queue, event);
|
||||
sem_post(event_loop->semaphore);
|
||||
}
|
||||
|
||||
bool event_loop_init(struct event_loop *event_loop)
|
||||
{
|
||||
if (!queue_init(&event_loop->queue)) return false;
|
||||
if (!memory_pool_init(&event_loop->pool, EVENT_POOL_SIZE)) return false;
|
||||
event_loop->is_running = false;
|
||||
#ifdef DEBUG
|
||||
event_loop->count = 0;
|
||||
#endif
|
||||
#ifdef STATS
|
||||
setlocale(LC_ALL, ""); // For fprintf digit grouping
|
||||
#endif
|
||||
event_loop->semaphore = sem_open("spacebar_event_loop_semaphore", O_CREAT, 0600, 0);
|
||||
sem_unlink("spacebar_event_loop_semaphore");
|
||||
return event_loop->semaphore != SEM_FAILED;
|
||||
}
|
||||
|
||||
bool event_loop_begin(struct event_loop *event_loop)
|
||||
{
|
||||
if (event_loop->is_running) return false;
|
||||
event_loop->is_running = true;
|
||||
pthread_create(&event_loop->thread, NULL, &event_loop_run, event_loop);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool event_loop_end(struct event_loop *event_loop)
|
||||
{
|
||||
if (!event_loop->is_running) return false;
|
||||
event_loop->is_running = false;
|
||||
pthread_join(event_loop->thread, NULL);
|
||||
return true;
|
||||
}
|
43
src/event_loop.h
Normal file
43
src/event_loop.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#ifndef EVENT_LOOP_H
|
||||
#define EVENT_LOOP_H
|
||||
|
||||
#define EVENT_POOL_SIZE KILOBYTES(36)
|
||||
#define EVENT_MAX_COUNT ((EVENT_POOL_SIZE) / (sizeof(struct event)))
|
||||
|
||||
#define QUEUE_POOL_SIZE KILOBYTES(16)
|
||||
#define QUEUE_MAX_COUNT ((QUEUE_POOL_SIZE) / (sizeof(struct queue_item)))
|
||||
|
||||
struct queue_item
|
||||
{
|
||||
struct event *data;
|
||||
struct queue_item *next;
|
||||
};
|
||||
|
||||
struct queue
|
||||
{
|
||||
struct memory_pool pool;
|
||||
struct queue_item *head;
|
||||
struct queue_item *tail;
|
||||
#ifdef DEBUG
|
||||
volatile uint64_t count;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct event_loop
|
||||
{
|
||||
bool is_running;
|
||||
pthread_t thread;
|
||||
sem_t *semaphore;
|
||||
struct queue queue;
|
||||
struct memory_pool pool;
|
||||
#ifdef DEBUG
|
||||
volatile uint64_t count;
|
||||
#endif
|
||||
};
|
||||
|
||||
bool event_loop_init(struct event_loop *event_loop);
|
||||
bool event_loop_begin(struct event_loop *event_loop);
|
||||
bool event_loop_end(struct event_loop *event_loop);
|
||||
void event_loop_post(struct event_loop *event_loop, struct event *event);
|
||||
|
||||
#endif
|
66
src/manifest.m
Normal file
66
src/manifest.m
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include <ScriptingBridge/ScriptingBridge.h>
|
||||
#include <Carbon/Carbon.h>
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <IOKit/ps/IOPowerSources.h>
|
||||
#include <IOKit/ps/IOPSKeys.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <regex.h>
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <semaphore.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "misc/timing.h"
|
||||
#include "misc/macros.h"
|
||||
#include "misc/notify.h"
|
||||
#include "misc/log.h"
|
||||
#include "misc/helpers.h"
|
||||
#include "misc/memory_pool.h"
|
||||
#include "misc/sbuffer.h"
|
||||
#define HASHTABLE_IMPLEMENTATION
|
||||
#include "misc/hashtable.h"
|
||||
#undef HASHTABLE_IMPLEMENTATION
|
||||
#include "misc/socket.h"
|
||||
#include "misc/socket.c"
|
||||
|
||||
//#include "osax/sa.h"
|
||||
//#include "osax/sa_loader.c"
|
||||
//#include "osax/sa_payload.c"
|
||||
//#include "osax/sa.m"
|
||||
|
||||
#include "event_loop.h"
|
||||
#include "event.h"
|
||||
#include "workspace.h"
|
||||
#include "message.h"
|
||||
#include "display.h"
|
||||
#include "process_manager.h"
|
||||
#include "application.h"
|
||||
#include "display_manager.h"
|
||||
#include "application_manager.h"
|
||||
#include "bar.h"
|
||||
#include "bar_manager.h"
|
||||
|
||||
#include "event_loop.c"
|
||||
#include "event.c"
|
||||
#include "workspace.m"
|
||||
#include "message.c"
|
||||
#include "display.c"
|
||||
#include "process_manager.c"
|
||||
#include "application.c"
|
||||
#include "display_manager.c"
|
||||
#include "bar.c"
|
||||
#include "bar_manager.c"
|
||||
#include "application_manager.c"
|
||||
|
||||
#include "spacebar.c"
|
229
src/message.c
Normal file
229
src/message.c
Normal file
|
@ -0,0 +1,229 @@
|
|||
#include "message.h"
|
||||
|
||||
extern struct event_loop g_event_loop;
|
||||
extern struct display_manager g_display_manager;
|
||||
extern struct space_manager g_space_manager;
|
||||
extern struct window_manager g_window_manager;
|
||||
extern struct mouse_state g_mouse_state;
|
||||
extern struct bar_manager g_bar_manager;
|
||||
extern bool g_verbose;
|
||||
|
||||
#define DOMAIN_CONFIG "config"
|
||||
|
||||
/* --------------------------------DOMAIN CONFIG-------------------------------- */
|
||||
#define COMMAND_CONFIG_DEBUG_OUTPUT "debug_output"
|
||||
#define COMMAND_CONFIG_BAR_TEXT_FONT "status_bar_text_font"
|
||||
#define COMMAND_CONFIG_BAR_ICON_FONT "status_bar_icon_font"
|
||||
#define COMMAND_CONFIG_BAR_BACKGROUND "status_bar_background_color"
|
||||
#define COMMAND_CONFIG_BAR_FOREGROUND "status_bar_foreground_color"
|
||||
#define COMMAND_CONFIG_BAR_SPACE_STRIP "status_bar_space_icon_strip"
|
||||
#define COMMAND_CONFIG_BAR_POWER_STRIP "status_bar_power_icon_strip"
|
||||
#define COMMAND_CONFIG_BAR_SPACE_ICON "status_bar_space_icon"
|
||||
#define COMMAND_CONFIG_BAR_CLOCK_ICON "status_bar_clock_icon"
|
||||
|
||||
/* --------------------------------COMMON ARGUMENTS----------------------------- */
|
||||
#define ARGUMENT_COMMON_VAL_ON "on"
|
||||
#define ARGUMENT_COMMON_VAL_OFF "off"
|
||||
|
||||
static bool token_equals(struct token token, char *match)
|
||||
{
|
||||
char *at = match;
|
||||
for (int i = 0; i < token.length; ++i, ++at) {
|
||||
if ((*at == 0) || (token.text[i] != *at)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return *at == 0;
|
||||
}
|
||||
|
||||
static bool token_is_valid(struct token token)
|
||||
{
|
||||
return token.text && token.length > 0;
|
||||
}
|
||||
|
||||
static char *token_to_string(struct token token)
|
||||
{
|
||||
char *result = malloc(token.length + 1);
|
||||
if (!result) return NULL;
|
||||
|
||||
memcpy(result, token.text, token.length);
|
||||
result[token.length] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint32_t token_to_uint32t(struct token token)
|
||||
{
|
||||
uint32_t result = 0;
|
||||
char buffer[token.length + 1];
|
||||
memcpy(buffer, token.text, token.length);
|
||||
buffer[token.length] = '\0';
|
||||
sscanf(buffer, "%x", &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static struct token get_token(char **message)
|
||||
{
|
||||
struct token token;
|
||||
|
||||
token.text = *message;
|
||||
while (**message) {
|
||||
++(*message);
|
||||
}
|
||||
token.length = *message - token.text;
|
||||
|
||||
if ((*message)[0] == '\0' && (*message)[1] != '\0') {
|
||||
++(*message);
|
||||
} else {
|
||||
// NOTE(koekeishiya): don't go past the null-terminator
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
static void daemon_fail(FILE *rsp, char *fmt, ...)
|
||||
{
|
||||
if (!rsp) return;
|
||||
|
||||
fprintf(rsp, FAILURE_MESSAGE);
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vfprintf(rsp, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
#define VIEW_SET_PROPERTY(p) \
|
||||
int p_val = 0; \
|
||||
if (token_to_int(value, &p_val)) { \
|
||||
view->custom_##p = true; \
|
||||
view->p = p_val; \
|
||||
view_update(view); \
|
||||
view_flush(view); \
|
||||
}
|
||||
|
||||
static void handle_domain_config(FILE *rsp, struct token domain, char *message)
|
||||
{
|
||||
struct token command = get_token(&message);
|
||||
|
||||
if (token_equals(command, COMMAND_CONFIG_DEBUG_OUTPUT)) {
|
||||
struct token value = get_token(&message);
|
||||
if (!token_is_valid(value)) {
|
||||
fprintf(rsp, "%s\n", bool_str[g_verbose]);
|
||||
} else if (token_equals(value, ARGUMENT_COMMON_VAL_OFF)) {
|
||||
g_verbose = false;
|
||||
} else if (token_equals(value, ARGUMENT_COMMON_VAL_ON)) {
|
||||
g_verbose = true;
|
||||
} else {
|
||||
daemon_fail(rsp, "unknown value '%.*s' given to command '%.*s' for domain '%.*s'\n", value.length, value.text, command.length, command.text, domain.length, domain.text);
|
||||
}
|
||||
} else if (token_equals(command, COMMAND_CONFIG_BAR_TEXT_FONT)) {
|
||||
int length = strlen(message);
|
||||
if (length <= 0) {
|
||||
fprintf(rsp, "%s\n", g_bar_manager.t_font_prop);
|
||||
} else {
|
||||
bar_manager_set_text_font(&g_bar_manager, string_copy(message));
|
||||
}
|
||||
} else if (token_equals(command, COMMAND_CONFIG_BAR_ICON_FONT)) {
|
||||
int length = strlen(message);
|
||||
if (length <= 0) {
|
||||
fprintf(rsp, "%s\n", g_bar_manager.i_font_prop);
|
||||
} else {
|
||||
bar_manager_set_icon_font(&g_bar_manager, string_copy(message));
|
||||
}
|
||||
} else if (token_equals(command, COMMAND_CONFIG_BAR_BACKGROUND)) {
|
||||
struct token value = get_token(&message);
|
||||
if (!token_is_valid(value)) {
|
||||
fprintf(rsp, "0x%x\n", g_bar_manager.background_color.p);
|
||||
} else {
|
||||
uint32_t color = token_to_uint32t(value);
|
||||
if (color) {
|
||||
bar_manager_set_background_color(&g_bar_manager, color);
|
||||
} else {
|
||||
daemon_fail(rsp, "unknown value '%.*s' given to command '%.*s' for domain '%.*s'\n", value.length, value.text, command.length, command.text, domain.length, domain.text);
|
||||
}
|
||||
}
|
||||
} else if (token_equals(command, COMMAND_CONFIG_BAR_FOREGROUND)) {
|
||||
struct token value = get_token(&message);
|
||||
if (!token_is_valid(value)) {
|
||||
fprintf(rsp, "0x%x\n", g_bar_manager.foreground_color.p);
|
||||
} else {
|
||||
uint32_t color = token_to_uint32t(value);
|
||||
if (color) {
|
||||
bar_manager_set_foreground_color(&g_bar_manager, color);
|
||||
} else {
|
||||
daemon_fail(rsp, "unknown value '%.*s' given to command '%.*s' for domain '%.*s'\n", value.length, value.text, command.length, command.text, domain.length, domain.text);
|
||||
}
|
||||
}
|
||||
} else if (token_equals(command, COMMAND_CONFIG_BAR_SPACE_STRIP)) {
|
||||
char **icon_strip = NULL;
|
||||
struct token token = get_token(&message);
|
||||
while (token.text && token.length > 0) {
|
||||
buf_push(icon_strip, token_to_string(token));
|
||||
token = get_token(&message);
|
||||
}
|
||||
bar_manager_set_space_strip(&g_bar_manager, icon_strip);
|
||||
} else if (token_equals(command, COMMAND_CONFIG_BAR_POWER_STRIP)) {
|
||||
char **icon_strip = NULL;
|
||||
struct token token = get_token(&message);
|
||||
while (token.text && token.length > 0) {
|
||||
buf_push(icon_strip, token_to_string(token));
|
||||
token = get_token(&message);
|
||||
}
|
||||
bar_manager_set_power_strip(&g_bar_manager, icon_strip);
|
||||
if (buf_len(g_bar_manager._power_icon_strip) != 2) {
|
||||
daemon_fail(rsp, "value for '%.*s' must contain exactly two symbols separated by whitespace.\n", command.length, command.text);
|
||||
}
|
||||
} else if (token_equals(command, COMMAND_CONFIG_BAR_SPACE_ICON)) {
|
||||
struct token token = get_token(&message);
|
||||
if (!token_is_valid(token)) {
|
||||
fprintf(rsp, "%s\n", g_bar_manager._space_icon ? g_bar_manager._space_icon : "");
|
||||
} else {
|
||||
bar_manager_set_space_icon(&g_bar_manager, token_to_string(token));
|
||||
}
|
||||
} else if (token_equals(command, COMMAND_CONFIG_BAR_CLOCK_ICON)) {
|
||||
struct token token = get_token(&message);
|
||||
if (!token_is_valid(token)) {
|
||||
fprintf(rsp, "%s\n", g_bar_manager._clock_icon ? g_bar_manager._clock_icon : "");
|
||||
} else {
|
||||
bar_manager_set_clock_icon(&g_bar_manager, token_to_string(token));
|
||||
}
|
||||
} else {
|
||||
daemon_fail(rsp, "unknown command '%.*s' for domain '%.*s'\n", command.length, command.text, domain.length, domain.text);
|
||||
}
|
||||
}
|
||||
|
||||
#undef VIEW_SET_PROPERTY
|
||||
|
||||
struct selector
|
||||
{
|
||||
struct token token;
|
||||
bool did_parse;
|
||||
|
||||
union {
|
||||
int dir;
|
||||
uint32_t did;
|
||||
uint64_t sid;
|
||||
struct window *window;
|
||||
};
|
||||
};
|
||||
|
||||
enum label_type
|
||||
{
|
||||
LABEL_SPACE,
|
||||
};
|
||||
|
||||
void handle_message(FILE *rsp, char *message)
|
||||
{
|
||||
struct token domain = get_token(&message);
|
||||
if (token_equals(domain, DOMAIN_CONFIG)) {
|
||||
handle_domain_config(rsp, domain, message);
|
||||
} else {
|
||||
daemon_fail(rsp, "unknown domain '%.*s'\n", domain.length, domain.text);
|
||||
}
|
||||
}
|
||||
|
||||
static SOCKET_DAEMON_HANDLER(message_handler)
|
||||
{
|
||||
struct event *event = event_create_p1(&g_event_loop, DAEMON_MESSAGE, message, sockfd);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
}
|
13
src/message.h
Normal file
13
src/message.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifndef MESSAGE_H
|
||||
#define MESSAGE_H
|
||||
|
||||
struct token
|
||||
{
|
||||
char *text;
|
||||
unsigned int length;
|
||||
};
|
||||
|
||||
static SOCKET_DAEMON_HANDLER(message_handler);
|
||||
void handle_message(FILE *rsp, char *message);
|
||||
|
||||
#endif
|
147
src/misc/hashtable.h
Normal file
147
src/misc/hashtable.h
Normal file
|
@ -0,0 +1,147 @@
|
|||
#ifndef HASHTABLE_H
|
||||
#define HASHTABLE_H
|
||||
|
||||
#define TABLE_HASH_FUNC(name) unsigned long name(void *key)
|
||||
typedef TABLE_HASH_FUNC(table_hash_func);
|
||||
|
||||
#define TABLE_COMPARE_FUNC(name) int name(void *key_a, void *key_b)
|
||||
typedef TABLE_COMPARE_FUNC(table_compare_func);
|
||||
|
||||
struct bucket
|
||||
{
|
||||
void *key;
|
||||
void *value;
|
||||
struct bucket *next;
|
||||
};
|
||||
struct table
|
||||
{
|
||||
int count;
|
||||
int capacity;
|
||||
float max_load;
|
||||
table_hash_func *hash;
|
||||
table_compare_func *cmp;
|
||||
struct bucket **buckets;
|
||||
};
|
||||
|
||||
void table_init(struct table *table, int capacity, table_hash_func hash, table_compare_func cmp);
|
||||
void table_free(struct table *table);
|
||||
|
||||
#define table_add(table, key, value) _table_add(table, key, sizeof(*key), value)
|
||||
void _table_add(struct table *table, void *key, int key_size, void *value);
|
||||
void table_remove(struct table *table, void *key);
|
||||
void *table_find(struct table *table, void *key);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HASHTABLE_IMPLEMENTATION
|
||||
void table_init(struct table *table, int capacity, table_hash_func hash, table_compare_func cmp)
|
||||
{
|
||||
table->count = 0;
|
||||
table->capacity = capacity;
|
||||
table->max_load = 0.75f;
|
||||
table->hash = hash;
|
||||
table->cmp = cmp;
|
||||
table->buckets = malloc(sizeof(struct bucket *) * capacity);
|
||||
memset(table->buckets, 0, sizeof(struct bucket *) * capacity);
|
||||
}
|
||||
|
||||
void table_free(struct table *table)
|
||||
{
|
||||
for (int i = 0; i < table->capacity; ++i) {
|
||||
struct bucket *next, *bucket = table->buckets[i];
|
||||
while (bucket) {
|
||||
next = bucket->next;
|
||||
free(bucket->key);
|
||||
free(bucket);
|
||||
bucket = next;
|
||||
}
|
||||
}
|
||||
|
||||
if (table->buckets) {
|
||||
free(table->buckets);
|
||||
table->buckets = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static struct bucket **
|
||||
table_get_bucket(struct table *table, void *key)
|
||||
{
|
||||
struct bucket **bucket = table->buckets + (table->hash(key) % table->capacity);
|
||||
while (*bucket) {
|
||||
if (table->cmp((*bucket)->key, key)) {
|
||||
break;
|
||||
}
|
||||
bucket = &(*bucket)->next;
|
||||
}
|
||||
return bucket;
|
||||
}
|
||||
|
||||
static void
|
||||
table_rehash(struct table *table)
|
||||
{
|
||||
struct bucket **old_buckets = table->buckets;
|
||||
int old_capacity = table->capacity;
|
||||
|
||||
table->count = 0;
|
||||
table->capacity = 2 * table->capacity;
|
||||
table->buckets = malloc(sizeof(struct bucket *) * table->capacity);
|
||||
memset(table->buckets, 0, sizeof(struct bucket *) * table->capacity);
|
||||
|
||||
for (int i = 0; i < old_capacity; ++i) {
|
||||
struct bucket *next_bucket, *old_bucket = old_buckets[i];
|
||||
while (old_bucket) {
|
||||
struct bucket **new_bucket = table_get_bucket(table, old_bucket->key);
|
||||
*new_bucket = malloc(sizeof(struct bucket));
|
||||
(*new_bucket)->key = old_bucket->key;
|
||||
(*new_bucket)->value = old_bucket->value;
|
||||
(*new_bucket)->next = NULL;
|
||||
++table->count;
|
||||
next_bucket = old_bucket->next;
|
||||
free(old_bucket);
|
||||
old_bucket = next_bucket;
|
||||
}
|
||||
}
|
||||
|
||||
free(old_buckets);
|
||||
}
|
||||
|
||||
void _table_add(struct table *table, void *key, int key_size, void *value)
|
||||
{
|
||||
struct bucket **bucket = table_get_bucket(table, key);
|
||||
if (*bucket) {
|
||||
if (!(*bucket)->value) {
|
||||
(*bucket)->value = value;
|
||||
}
|
||||
} else {
|
||||
*bucket = malloc(sizeof(struct bucket));
|
||||
(*bucket)->key = malloc(key_size);
|
||||
(*bucket)->value = value;
|
||||
memcpy((*bucket)->key, key, key_size);
|
||||
(*bucket)->next = NULL;
|
||||
++table->count;
|
||||
|
||||
float load = (1.0f * table->count) / table->capacity;
|
||||
if (load > table->max_load) {
|
||||
table_rehash(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void table_remove(struct table *table, void *key)
|
||||
{
|
||||
struct bucket *next, **bucket = table_get_bucket(table, key);
|
||||
if (*bucket) {
|
||||
free((*bucket)->key);
|
||||
next = (*bucket)->next;
|
||||
free(*bucket);
|
||||
*bucket = next;
|
||||
--table->count;
|
||||
}
|
||||
}
|
||||
|
||||
void *table_find(struct table *table, void *key)
|
||||
{
|
||||
struct bucket *bucket = *table_get_bucket(table, key);
|
||||
return bucket ? bucket->value : NULL;
|
||||
}
|
||||
#endif
|
185
src/misc/helpers.h
Normal file
185
src/misc/helpers.h
Normal file
|
@ -0,0 +1,185 @@
|
|||
#ifndef HELPERS_H
|
||||
#define HELPERS_H
|
||||
|
||||
extern AXError _AXUIElementGetWindow(AXUIElementRef ref, uint32_t *wid);
|
||||
|
||||
static const char *bool_str[] = { "off", "on" };
|
||||
|
||||
struct signal_args
|
||||
{
|
||||
char name[2][255];
|
||||
char value[2][255];
|
||||
void *entity;
|
||||
void *param1;
|
||||
};
|
||||
|
||||
struct rgba_color
|
||||
{
|
||||
bool is_valid;
|
||||
uint32_t p;
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
};
|
||||
|
||||
static struct rgba_color
|
||||
rgba_color_from_hex(uint32_t color)
|
||||
{
|
||||
struct rgba_color result;
|
||||
result.is_valid = true;
|
||||
result.p = color;
|
||||
result.r = ((color >> 16) & 0xff) / 255.0;
|
||||
result.g = ((color >> 8) & 0xff) / 255.0;
|
||||
result.b = ((color >> 0) & 0xff) / 255.0;
|
||||
result.a = ((color >> 24) & 0xff) / 255.0;
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline bool is_root(void)
|
||||
{
|
||||
return getuid() == 0 || geteuid() == 0;
|
||||
}
|
||||
|
||||
static inline bool string_equals(const char *a, const char *b)
|
||||
{
|
||||
return a && b && strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
static inline char *string_escape_quote(char *s)
|
||||
{
|
||||
if (!s) return NULL;
|
||||
|
||||
char *cursor = s;
|
||||
int num_quotes = 0;
|
||||
|
||||
while (*cursor) {
|
||||
if (*cursor == '"') ++num_quotes;
|
||||
++cursor;
|
||||
}
|
||||
|
||||
if (!num_quotes) return NULL;
|
||||
|
||||
int size_in_bytes = (int)(cursor - s) + num_quotes;
|
||||
char *result = malloc(sizeof(char) * (size_in_bytes+1));
|
||||
result[size_in_bytes] = '\0';
|
||||
|
||||
for (char *dst = result, *cursor = s; *cursor; ++cursor) {
|
||||
if (*cursor == '"') *dst++ = '\\';
|
||||
*dst++ = *cursor;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline char *cfstring_copy(CFStringRef string)
|
||||
{
|
||||
CFIndex num_bytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8);
|
||||
char *result = malloc(num_bytes + 1);
|
||||
if (!result) return NULL;
|
||||
|
||||
if (!CFStringGetCString(string, result, num_bytes + 1, kCFStringEncodingUTF8)) {
|
||||
free(result);
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline char *string_copy(char *s)
|
||||
{
|
||||
int length = strlen(s);
|
||||
char *result = malloc(length + 1);
|
||||
if (!result) return NULL;
|
||||
|
||||
memcpy(result, s, length);
|
||||
result[length] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline bool file_exists(char *filename)
|
||||
{
|
||||
struct stat buffer;
|
||||
|
||||
if (stat(filename, &buffer) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer.st_mode & S_IFDIR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool ensure_executable_permission(char *filename)
|
||||
{
|
||||
struct stat buffer;
|
||||
|
||||
if (stat(filename, &buffer) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_executable = buffer.st_mode & S_IXUSR;
|
||||
if (!is_executable && chmod(filename, S_IXUSR | buffer.st_mode) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fork_exec(char *command, struct signal_args *args)
|
||||
{
|
||||
int pid = fork();
|
||||
if (pid == -1) return false;
|
||||
if (pid != 0) return true;
|
||||
|
||||
if (args) {
|
||||
if (*args->name[0]) setenv(args->name[0], args->value[0], 1);
|
||||
if (*args->name[1]) setenv(args->name[1], args->value[1], 1);
|
||||
}
|
||||
|
||||
char *exec[] = { "/usr/bin/env", "sh", "-c", command, NULL};
|
||||
exit(execvp(exec[0], exec));
|
||||
}
|
||||
|
||||
static bool ax_privilege(void)
|
||||
{
|
||||
const void *keys[] = { kAXTrustedCheckOptionPrompt };
|
||||
const void *values[] = { kCFBooleanTrue };
|
||||
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, array_count(keys), &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
bool result = AXIsProcessTrustedWithOptions(options);
|
||||
CFRelease(options);
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline uint32_t ax_window_id(AXUIElementRef ref)
|
||||
{
|
||||
uint32_t wid = 0;
|
||||
_AXUIElementGetWindow(ref, &wid);
|
||||
return wid;
|
||||
}
|
||||
|
||||
static inline pid_t ax_window_pid(AXUIElementRef ref)
|
||||
{
|
||||
return *(pid_t *)((void *) ref + 0x10);
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
static inline bool psn_equals(ProcessSerialNumber *a, ProcessSerialNumber *b)
|
||||
{
|
||||
Boolean result;
|
||||
SameProcess(a, b, &result);
|
||||
return result == 1;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
static inline float clampf_range(float value, float min, float max)
|
||||
{
|
||||
if (value < min) return min;
|
||||
if (value > max) return max;
|
||||
return value;
|
||||
}
|
||||
|
||||
#endif
|
36
src/misc/log.h
Normal file
36
src/misc/log.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef LOG_H
|
||||
#define LOG_H
|
||||
|
||||
extern bool g_verbose;
|
||||
|
||||
static inline void
|
||||
debug(const char *format, ...)
|
||||
{
|
||||
if (!g_verbose) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stdout, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static inline void
|
||||
warn(const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static inline void
|
||||
error(const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#endif
|
33
src/misc/macros.h
Normal file
33
src/misc/macros.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#ifndef MACROS_H
|
||||
#define MACROS_H
|
||||
|
||||
#define array_count(a) (sizeof((a)) / sizeof(*(a)))
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||
#define add_and_clamp_to_zero(a, b) (((a) + (b) <= 0) ? 0 : (a) + (b))
|
||||
|
||||
#define MAXLEN 512
|
||||
|
||||
#define REGEX_MATCH_UD 0
|
||||
#define REGEX_MATCH_YES 1
|
||||
#define REGEX_MATCH_NO 2
|
||||
|
||||
#define DIR_NORTH 360
|
||||
#define DIR_EAST 90
|
||||
#define DIR_SOUTH 180
|
||||
#define DIR_WEST 270
|
||||
|
||||
#define TYPE_ABS 0x1
|
||||
#define TYPE_REL 0x2
|
||||
|
||||
#define HANDLE_TOP 0x01
|
||||
#define HANDLE_BOTTOM 0x02
|
||||
#define HANDLE_LEFT 0x04
|
||||
#define HANDLE_RIGHT 0x08
|
||||
#define HANDLE_ABS 0x10
|
||||
|
||||
#define LAYER_BELOW kCGBackstopMenuLevelKey
|
||||
#define LAYER_NORMAL kCGNormalWindowLevelKey
|
||||
#define LAYER_ABOVE kCGFloatingWindowLevelKey
|
||||
|
||||
#endif
|
42
src/misc/memory_pool.h
Normal file
42
src/misc/memory_pool.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
#ifndef MEMORY_POOL_H
|
||||
#define MEMORY_POOL_H
|
||||
|
||||
#define KILOBYTES(value) ((value) * 1024ULL)
|
||||
#define MEGABYTES(value) (KILOBYTES(value) * 1024ULL)
|
||||
#define GIGABYTES(value) (MEGABYTES(value) * 1024ULL)
|
||||
|
||||
struct memory_pool
|
||||
{
|
||||
void *memory;
|
||||
uint64_t size;
|
||||
volatile uint64_t used;
|
||||
};
|
||||
|
||||
bool memory_pool_init(struct memory_pool *pool, uint64_t size)
|
||||
{
|
||||
pool->used = 0;
|
||||
pool->size = size;
|
||||
pool->memory = mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
|
||||
return pool->memory != NULL;
|
||||
}
|
||||
|
||||
#define memory_pool_push(p, t) memory_pool_push_size(p, sizeof(t))
|
||||
void *memory_pool_push_size(struct memory_pool *pool, uint64_t size)
|
||||
{
|
||||
for (;;) {
|
||||
uint64_t used = pool->used;
|
||||
uint64_t new_used = used + size;
|
||||
|
||||
if (new_used < pool->size) {
|
||||
if (__sync_bool_compare_and_swap(&pool->used, used, new_used)) {
|
||||
return pool->memory + used;
|
||||
}
|
||||
} else {
|
||||
if (__sync_bool_compare_and_swap(&pool->used, used, size)) {
|
||||
return pool->memory;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
47
src/misc/notify.h
Normal file
47
src/misc/notify.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
static bool g_notify_init;
|
||||
static NSImage *g_notify_img;
|
||||
|
||||
@implementation NSBundle(swizzle)
|
||||
- (NSString *)fake_bundleIdentifier
|
||||
{
|
||||
if (self == [NSBundle mainBundle]) {
|
||||
return @"com.somdoron.spacebar";
|
||||
} else {
|
||||
return [self fake_bundleIdentifier];
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
static bool notify_init(void)
|
||||
{
|
||||
Class c = objc_getClass("NSBundle");
|
||||
if (!c) return false;
|
||||
|
||||
method_exchangeImplementations(class_getInstanceMethod(c, @selector(bundleIdentifier)), class_getInstanceMethod(c, @selector(fake_bundleIdentifier)));
|
||||
g_notify_img = [[[NSWorkspace sharedWorkspace] iconForFile:[[[NSBundle mainBundle] executablePath] stringByResolvingSymlinksInPath]] retain];
|
||||
g_notify_init = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void notify(const char *subtitle, const char *format, ...)
|
||||
{
|
||||
@autoreleasepool {
|
||||
if (!g_notify_init) notify_init();
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
NSUserNotification *notification = [[NSUserNotification alloc] init];
|
||||
notification.title = @"spacebar";
|
||||
notification.subtitle = [NSString stringWithUTF8String:subtitle];
|
||||
notification.informativeText = [[[NSString alloc] initWithFormat:[NSString stringWithUTF8String:format] arguments:args] autorelease];
|
||||
[notification setValue:g_notify_img forKey:@"_identityImage"];
|
||||
[notification setValue:@(false) forKey:@"_identityImageHasBorder"];
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
|
||||
[notification release];
|
||||
va_end(args);
|
||||
}
|
||||
}
|
36
src/misc/sbuffer.h
Normal file
36
src/misc/sbuffer.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef SBUFFER_H
|
||||
#define SBUFFER_H
|
||||
|
||||
struct buf_hdr
|
||||
{
|
||||
size_t len;
|
||||
size_t cap;
|
||||
char buf[0];
|
||||
};
|
||||
|
||||
#define OFFSETOF(t, f) (size_t)((char *)&(((t *)0)->f) - (char *)0)
|
||||
|
||||
#define buf__hdr(b) ((struct buf_hdr *)((char *)(b) - OFFSETOF(struct buf_hdr, buf)))
|
||||
#define buf__should_grow(b, n) (buf_len(b) + (n) >= buf_cap(b))
|
||||
#define buf__fit(b, n) (buf__should_grow(b, n) ? ((b) = buf__grow_f(b, buf_len(b) + (n), sizeof(*(b)))) : 0)
|
||||
|
||||
#define buf_len(b) ((b) ? buf__hdr(b)->len : 0)
|
||||
#define buf_cap(b) ((b) ? buf__hdr(b)->cap : 0)
|
||||
#define buf_last(b) ((b)[buf_len(b)-1])
|
||||
#define buf_push(b, x) (buf__fit(b, 1), (b)[buf_len(b)] = (x), buf__hdr(b)->len++)
|
||||
#define buf_del(b, x) ((b) ? (b)[x] = (b)[buf_len(b)-1], buf__hdr(b)->len-- : 0)
|
||||
#define buf_free(b) ((b) ? free(buf__hdr(b)) : 0)
|
||||
|
||||
static void *buf__grow_f(const void *buf, size_t new_len, size_t elem_size)
|
||||
{
|
||||
size_t new_cap = max(1 + 2*buf_cap(buf), new_len);
|
||||
size_t new_size = OFFSETOF(struct buf_hdr, buf) + new_cap*elem_size;
|
||||
struct buf_hdr *new_hdr = realloc(buf ? buf__hdr(buf) : 0, new_size);
|
||||
new_hdr->cap = new_cap;
|
||||
if (!buf) {
|
||||
new_hdr->len = 0;
|
||||
}
|
||||
return new_hdr->buf;
|
||||
}
|
||||
|
||||
#endif
|
175
src/misc/socket.c
Normal file
175
src/misc/socket.c
Normal file
|
@ -0,0 +1,175 @@
|
|||
#include "socket.h"
|
||||
|
||||
char *socket_read(int sockfd, int *len)
|
||||
{
|
||||
int cursor = 0;
|
||||
int bytes_read = 0;
|
||||
char *result = NULL;
|
||||
char buffer[BUFSIZ];
|
||||
|
||||
while ((bytes_read = read(sockfd, buffer, sizeof(buffer)-1)) > 0) {
|
||||
char *temp = realloc(result, cursor+bytes_read+1);
|
||||
if (!temp) goto err;
|
||||
|
||||
result = temp;
|
||||
memcpy(result+cursor, buffer, bytes_read);
|
||||
cursor += bytes_read;
|
||||
}
|
||||
|
||||
if (result && bytes_read != -1) {
|
||||
result[cursor] = '\0';
|
||||
*len = cursor;
|
||||
} else {
|
||||
err:
|
||||
if (result) free(result);
|
||||
result = NULL;
|
||||
*len = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool socket_write_bytes(int sockfd, char *message, int len)
|
||||
{
|
||||
return send(sockfd, message, len, 0) != -1;
|
||||
}
|
||||
|
||||
bool socket_write(int sockfd, char *message)
|
||||
{
|
||||
return send(sockfd, message, strlen(message), 0) != -1;
|
||||
}
|
||||
|
||||
bool socket_connect_in(int *sockfd, int port)
|
||||
{
|
||||
struct sockaddr_in socket_address;
|
||||
|
||||
*sockfd = socket(PF_INET, SOCK_STREAM, 0);
|
||||
if (*sockfd == -1) return false;
|
||||
|
||||
socket_address.sin_family = AF_INET;
|
||||
socket_address.sin_port = htons(port);
|
||||
socket_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
memset(&socket_address.sin_zero, '\0', 8);
|
||||
|
||||
return connect(*sockfd, (struct sockaddr*) &socket_address, sizeof(struct sockaddr)) != -1;
|
||||
}
|
||||
|
||||
bool socket_connect_un(int *sockfd, char *socket_path)
|
||||
{
|
||||
struct sockaddr_un socket_address;
|
||||
socket_address.sun_family = AF_UNIX;
|
||||
|
||||
*sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (*sockfd == -1) return false;
|
||||
|
||||
snprintf(socket_address.sun_path, sizeof(socket_address.sun_path), "%s", socket_path);
|
||||
return connect(*sockfd, (struct sockaddr *) &socket_address, sizeof(socket_address)) != -1;
|
||||
}
|
||||
|
||||
void socket_wait(int sockfd)
|
||||
{
|
||||
struct pollfd fds[] = {
|
||||
{ sockfd, POLLIN, 0 }
|
||||
};
|
||||
|
||||
char dummy[1];
|
||||
int bytes = 0;
|
||||
|
||||
while (poll(fds, 1, -1) > 0) {
|
||||
if (fds[0].revents & POLLIN) {
|
||||
if ((bytes = recv(sockfd, dummy, 0, 0)) <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void socket_close(int sockfd)
|
||||
{
|
||||
shutdown(sockfd, SHUT_RDWR);
|
||||
close(sockfd);
|
||||
}
|
||||
|
||||
static void *socket_connection_handler(void *context)
|
||||
{
|
||||
struct daemon *daemon = context;
|
||||
|
||||
while (daemon->is_running) {
|
||||
int sockfd = accept(daemon->sockfd, NULL, 0);
|
||||
if (sockfd == -1) continue;
|
||||
|
||||
int length;
|
||||
char *message = socket_read(sockfd, &length);
|
||||
if (message) {
|
||||
daemon->handler(message, length, sockfd);
|
||||
} else {
|
||||
socket_close(sockfd);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool socket_daemon_begin_in(struct daemon *daemon, int port, socket_daemon_handler *handler)
|
||||
{
|
||||
struct sockaddr_in socket_address;
|
||||
socket_address.sin_family = AF_INET;
|
||||
socket_address.sin_port = htons(port);
|
||||
socket_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
memset(&socket_address.sin_zero, '\0', 8);
|
||||
|
||||
if ((daemon->sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bind(daemon->sockfd, (struct sockaddr *) &socket_address, sizeof(socket_address)) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (listen(daemon->sockfd, SOMAXCONN) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
daemon->handler = handler;
|
||||
daemon->is_running = true;
|
||||
pthread_create(&daemon->thread, NULL, &socket_connection_handler, daemon);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool socket_daemon_begin_un(struct daemon *daemon, char *socket_path, socket_daemon_handler *handler)
|
||||
{
|
||||
struct sockaddr_un socket_address;
|
||||
socket_address.sun_family = AF_UNIX;
|
||||
snprintf(socket_address.sun_path, sizeof(socket_address.sun_path), "%s", socket_path);
|
||||
unlink(socket_path);
|
||||
|
||||
if ((daemon->sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bind(daemon->sockfd, (struct sockaddr *) &socket_address, sizeof(socket_address)) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chmod(socket_path, 0600) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (listen(daemon->sockfd, SOMAXCONN) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
daemon->handler = handler;
|
||||
daemon->is_running = true;
|
||||
pthread_create(&daemon->thread, NULL, &socket_connection_handler, daemon);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void socket_daemon_end(struct daemon *daemon)
|
||||
{
|
||||
daemon->is_running = false;
|
||||
pthread_join(daemon->thread, NULL);
|
||||
socket_close(daemon->sockfd);
|
||||
}
|
39
src/misc/socket.h
Normal file
39
src/misc/socket.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
#ifndef SOCKET_H
|
||||
#define SOCKET_H
|
||||
|
||||
#define SOCKET_DAEMON_HANDLER(name) void name(char *message, int length, int sockfd)
|
||||
typedef SOCKET_DAEMON_HANDLER(socket_daemon_handler);
|
||||
|
||||
#define FAILURE_MESSAGE "\x07"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/un.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <netdb.h>
|
||||
#include <poll.h>
|
||||
|
||||
struct daemon
|
||||
{
|
||||
int sockfd;
|
||||
bool is_running;
|
||||
pthread_t thread;
|
||||
socket_daemon_handler *handler;
|
||||
};
|
||||
|
||||
char *socket_read(int sockfd, int *len);
|
||||
bool socket_write_bytes(int sockfd, char *message, int len);
|
||||
bool socket_write(int sockfd, char *message);
|
||||
bool socket_connect_in(int *sockfd, int port);
|
||||
bool socket_connect_un(int *sockfd, char *socket_path);
|
||||
void socket_wait(int sockfd);
|
||||
void socket_close(int sockfd);
|
||||
bool socket_daemon_begin_in(struct daemon *daemon, int port, socket_daemon_handler *handler);
|
||||
bool socket_daemon_begin_un(struct daemon *daemon, char *socket_path, socket_daemon_handler *handler);
|
||||
void socket_daemon_end(struct daemon *daemon);
|
||||
|
||||
#endif
|
29
src/misc/timing.h
Normal file
29
src/misc/timing.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include <CoreServices/CoreServices.h>
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
static inline uint64_t time_clock(void)
|
||||
{
|
||||
return mach_absolute_time();
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
static inline uint64_t time_elapsed_ns(uint64_t begin, uint64_t end)
|
||||
{
|
||||
uint64_t elapsed = end - begin;
|
||||
Nanoseconds nano = AbsoluteToNanoseconds(*(AbsoluteTime *) &elapsed);
|
||||
return *(uint64_t *) &nano;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
static inline double time_elapsed_ms(uint64_t begin, uint64_t end)
|
||||
{
|
||||
uint64_t ns = time_elapsed_ns(begin, end);
|
||||
return (double)(ns / 1000000.0);
|
||||
}
|
||||
|
||||
static inline double time_elapsed_s(uint64_t begin, uint64_t end)
|
||||
{
|
||||
uint64_t ns = time_elapsed_ns(begin, end);
|
||||
return (double)(ns / 1000000000.0);
|
||||
}
|
222
src/process_manager.c
Normal file
222
src/process_manager.c
Normal file
|
@ -0,0 +1,222 @@
|
|||
#include "process_manager.h"
|
||||
|
||||
extern struct event_loop g_event_loop;
|
||||
|
||||
static TABLE_HASH_FUNC(hash_psn)
|
||||
{
|
||||
unsigned long result = ((ProcessSerialNumber*) key)->lowLongOfPSN;
|
||||
result = (result + 0x7ed55d16) + (result << 12);
|
||||
result = (result ^ 0xc761c23c) ^ (result >> 19);
|
||||
result = (result + 0x165667b1) + (result << 5);
|
||||
result = (result + 0xd3a2646c) ^ (result << 9);
|
||||
result = (result + 0xfd7046c5) + (result << 3);
|
||||
result = (result ^ 0xb55a4f09) ^ (result >> 16);
|
||||
return result;
|
||||
}
|
||||
|
||||
static TABLE_COMPARE_FUNC(compare_psn)
|
||||
{
|
||||
return psn_equals(key_a, key_b);
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
struct process *process_create(ProcessSerialNumber psn)
|
||||
{
|
||||
struct process *process = malloc(sizeof(struct process));
|
||||
memset(process, 0, sizeof(struct process));
|
||||
|
||||
CFStringRef process_name_ref;
|
||||
if (CopyProcessName(&psn, &process_name_ref) == noErr) {
|
||||
process->name = cfstring_copy(process_name_ref);
|
||||
CFRelease(process_name_ref);
|
||||
} else {
|
||||
process->name = string_copy("<unknown>");
|
||||
}
|
||||
|
||||
ProcessInfoRec process_info = {};
|
||||
process_info.processInfoLength = sizeof(ProcessInfoRec);
|
||||
GetProcessInformation(&psn, &process_info);
|
||||
|
||||
process->psn = psn;
|
||||
GetProcessPID(&process->psn, &process->pid);
|
||||
process->background = (process_info.processMode & modeOnlyBackground) != 0;
|
||||
process->xpc = process_info.processType == 'XPC!';
|
||||
|
||||
CFDictionaryRef process_dict = ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask);
|
||||
if (process_dict) {
|
||||
CFBooleanRef process_lsuielement = CFDictionaryGetValue(process_dict, CFSTR("LSUIElement"));
|
||||
if (process_lsuielement) process->lsuielement = CFBooleanGetValue(process_lsuielement);
|
||||
CFBooleanRef process_lsbackground = CFDictionaryGetValue(process_dict, CFSTR("LSBackgroundOnly"));
|
||||
if (process_lsbackground) process->lsbackground = CFBooleanGetValue(process_lsbackground);
|
||||
CFRelease(process_dict);
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
void process_destroy(struct process *process)
|
||||
{
|
||||
free(process->name);
|
||||
free(process);
|
||||
}
|
||||
|
||||
static bool process_is_observable(struct process *process)
|
||||
{
|
||||
if (process->lsbackground) {
|
||||
debug("%s: %s was marked as background only! ignoring..\n", __FUNCTION__, process->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (process->lsuielement) {
|
||||
debug("%s: %s was marked as agent! ignoring..\n", __FUNCTION__, process->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (process->background) {
|
||||
debug("%s: %s was marked as daemon! ignoring..\n", __FUNCTION__, process->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (process->xpc) {
|
||||
debug("%s: %s was marked as xpc service! ignoring..\n", __FUNCTION__, process->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static PROCESS_EVENT_HANDLER(process_handler)
|
||||
{
|
||||
struct process_manager *pm = (struct process_manager *) user_data;
|
||||
|
||||
ProcessSerialNumber psn;
|
||||
if (GetEventParameter(event, kEventParamProcessID, typeProcessSerialNumber, NULL, sizeof(psn), NULL, &psn) != noErr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (GetEventKind(event)) {
|
||||
case kEventAppLaunched: {
|
||||
struct process *process = process_create(psn);
|
||||
if (!process) return noErr;
|
||||
|
||||
if (process_is_observable(process)) {
|
||||
struct event *event = event_create(&g_event_loop, APPLICATION_LAUNCHED, process);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
process_manager_add_process(pm, process);
|
||||
} else {
|
||||
process_destroy(process);
|
||||
}
|
||||
} break;
|
||||
case kEventAppTerminated: {
|
||||
struct process *process = process_manager_find_process(pm, &psn);
|
||||
if (!process) return noErr;
|
||||
|
||||
process->terminated = true;
|
||||
process_manager_remove_process(pm, &psn);
|
||||
|
||||
struct event *event = event_create(&g_event_loop, APPLICATION_TERMINATED, process);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
} break;
|
||||
case kEventAppFrontSwitched: {
|
||||
struct process *process = process_manager_find_process(pm, &psn);
|
||||
if (!process) return noErr;
|
||||
|
||||
struct event *event = event_create(&g_event_loop, APPLICATION_FRONT_SWITCHED, process);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
} break;
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static void
|
||||
process_manager_add_running_processes(struct process_manager *pm)
|
||||
{
|
||||
ProcessSerialNumber psn = { kNoProcess, kNoProcess };
|
||||
while (GetNextProcess(&psn) == noErr) {
|
||||
struct process *process = process_create(psn);
|
||||
if (!process) continue;
|
||||
|
||||
if (process_is_observable(process)) {
|
||||
if (string_equals(process->name, "Finder")) {
|
||||
debug("%s: %s was found! caching psn..\n", __FUNCTION__, process->name);
|
||||
pm->finder_psn = psn;
|
||||
}
|
||||
|
||||
process_manager_add_process(pm, process);
|
||||
} else {
|
||||
process_destroy(process);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
struct process *process_manager_find_process(struct process_manager *pm, ProcessSerialNumber *psn)
|
||||
{
|
||||
return table_find(&pm->process, psn);
|
||||
}
|
||||
|
||||
void process_manager_remove_process(struct process_manager *pm, ProcessSerialNumber *psn)
|
||||
{
|
||||
table_remove(&pm->process, psn);
|
||||
}
|
||||
|
||||
void process_manager_add_process(struct process_manager *pm, struct process *process)
|
||||
{
|
||||
table_add(&pm->process, &process->psn, process);
|
||||
}
|
||||
|
||||
#if 0
|
||||
bool process_manager_next_process(ProcessSerialNumber *next_psn)
|
||||
{
|
||||
CFArrayRef applications =_LSCopyApplicationArrayInFrontToBackOrder(0xFFFFFFFE, 1);
|
||||
if (!applications) return false;
|
||||
|
||||
bool found_front_psn = false;
|
||||
ProcessSerialNumber front_psn;
|
||||
_SLPSGetFrontProcess(&front_psn);
|
||||
|
||||
for (int i = 0; i < CFArrayGetCount(applications); ++i) {
|
||||
CFTypeRef asn = CFArrayGetValueAtIndex(applications, i);
|
||||
assert(CFGetTypeID(asn) == _LSASNGetTypeID());
|
||||
_LSASNExtractHighAndLowParts(asn, &next_psn->highLongOfPSN, &next_psn->lowLongOfPSN);
|
||||
if (found_front_psn) break;
|
||||
found_front_psn = psn_equals(&front_psn, next_psn);
|
||||
}
|
||||
|
||||
CFRelease(applications);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void process_manager_init(struct process_manager *pm)
|
||||
{
|
||||
pm->target = GetApplicationEventTarget();
|
||||
pm->handler = NewEventHandlerUPP(process_handler);
|
||||
pm->type[0].eventClass = kEventClassApplication;
|
||||
pm->type[0].eventKind = kEventAppLaunched;
|
||||
pm->type[1].eventClass = kEventClassApplication;
|
||||
pm->type[1].eventKind = kEventAppTerminated;
|
||||
pm->type[2].eventClass = kEventClassApplication;
|
||||
pm->type[2].eventKind = kEventAppFrontSwitched;
|
||||
table_init(&pm->process, 125, hash_psn, compare_psn);
|
||||
process_manager_add_running_processes(pm);
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
bool process_manager_begin(struct process_manager *pm)
|
||||
{
|
||||
ProcessSerialNumber front_psn;
|
||||
_SLPSGetFrontProcess(&front_psn);
|
||||
GetProcessPID(&front_psn, &g_process_manager.front_pid);
|
||||
g_process_manager.last_front_pid = g_process_manager.front_pid;
|
||||
return InstallEventHandler(pm->target, pm->handler, 3, pm->type, pm, &pm->ref) == noErr;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
bool process_manager_end(struct process_manager *pm)
|
||||
{
|
||||
return RemoveEventHandler(pm->ref) == noErr;
|
||||
}
|
51
src/process_manager.h
Normal file
51
src/process_manager.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
#ifndef PROCESS_MANAGER_H
|
||||
#define PROCESS_MANAGER_H
|
||||
|
||||
extern OSStatus _SLPSGetFrontProcess(ProcessSerialNumber *psn);
|
||||
extern CFStringRef SLSCopyBestManagedDisplayForRect(int cid, CGRect rect);
|
||||
extern CGError SLSGetCurrentCursorLocation(int cid, CGPoint *point);
|
||||
|
||||
#if 0
|
||||
extern CFArrayRef _LSCopyApplicationArrayInFrontToBackOrder(int negative_one, int one);
|
||||
extern void _LSASNExtractHighAndLowParts(const void *asn, uint32_t *high, uint32_t *low);
|
||||
extern CFTypeID _LSASNGetTypeID(void);
|
||||
#endif
|
||||
|
||||
#define PROCESS_EVENT_HANDLER(name) OSStatus name(EventHandlerCallRef ref, EventRef event, void *user_data)
|
||||
typedef PROCESS_EVENT_HANDLER(process_event_handler);
|
||||
|
||||
struct process
|
||||
{
|
||||
ProcessSerialNumber psn;
|
||||
pid_t pid;
|
||||
char *name;
|
||||
bool background;
|
||||
bool lsuielement;
|
||||
bool lsbackground;
|
||||
bool xpc;
|
||||
bool volatile terminated;
|
||||
};
|
||||
|
||||
struct process_manager
|
||||
{
|
||||
struct table process;
|
||||
EventTargetRef target;
|
||||
EventHandlerUPP handler;
|
||||
EventTypeSpec type[3];
|
||||
EventHandlerRef ref;
|
||||
pid_t front_pid;
|
||||
pid_t last_front_pid;
|
||||
ProcessSerialNumber finder_psn;
|
||||
};
|
||||
|
||||
void process_destroy(struct process *process);
|
||||
struct process *process_create(ProcessSerialNumber psn);
|
||||
struct process *process_manager_find_process(struct process_manager *pm, ProcessSerialNumber *psn);
|
||||
void process_manager_remove_process(struct process_manager *pm, ProcessSerialNumber *psn);
|
||||
void process_manager_add_process(struct process_manager *pm, struct process *process);
|
||||
// bool process_manager_next_process(ProcessSerialNumber *next_psn);
|
||||
void process_manager_init(struct process_manager *pm);
|
||||
bool process_manager_begin(struct process_manager *pm);
|
||||
bool process_manager_end(struct process_manager *pm);
|
||||
|
||||
#endif
|
268
src/spacebar.c
Normal file
268
src/spacebar.c
Normal file
|
@ -0,0 +1,268 @@
|
|||
#define SOCKET_PATH_FMT "/tmp/spacebar_%s.socket"
|
||||
#define LCFILE_PATH_FMT "/tmp/spacebar_%s.lock"
|
||||
|
||||
#define CLIENT_OPT_LONG "--message"
|
||||
#define CLIENT_OPT_SHRT "-m"
|
||||
|
||||
#define DEBUG_VERBOSE_OPT_LONG "--verbose"
|
||||
#define DEBUG_VERBOSE_OPT_SHRT "-V"
|
||||
#define VERSION_OPT_LONG "--version"
|
||||
#define VERSION_OPT_SHRT "-v"
|
||||
#define CONFIG_OPT_LONG "--config"
|
||||
#define CONFIG_OPT_SHRT "-c"
|
||||
|
||||
#define MAJOR 2
|
||||
#define MINOR 4
|
||||
#define PATCH 1
|
||||
|
||||
extern int SLSMainConnectionID(void);
|
||||
|
||||
#define CONNECTION_CALLBACK(name) void name(uint32_t type, void *data, size_t data_length, void *context, int cid)
|
||||
typedef CONNECTION_CALLBACK(connection_callback);
|
||||
extern CGError SLSRegisterConnectionNotifyProc(int cid, connection_callback *handler, uint32_t event, void *context);
|
||||
|
||||
struct event_loop g_event_loop;
|
||||
void *g_workspace_context;
|
||||
struct process_manager g_process_manager;
|
||||
struct display_manager g_display_manager;
|
||||
struct application_manager g_application_manager;
|
||||
struct daemon g_daemon;
|
||||
struct bar_manager g_bar_manager;
|
||||
int g_connection;
|
||||
|
||||
char g_socket_file[MAXLEN];
|
||||
char g_config_file[4096];
|
||||
char g_lock_file[MAXLEN];
|
||||
bool g_verbose;
|
||||
|
||||
static int client_send_message(int argc, char **argv)
|
||||
{
|
||||
if (argc <= 1) {
|
||||
error("spacebar-msg: no arguments given! abort..\n");
|
||||
}
|
||||
|
||||
char *user = getenv("USER");
|
||||
if (!user) {
|
||||
error("spacebar-msg: 'env USER' not set! abort..\n");
|
||||
}
|
||||
|
||||
int sockfd;
|
||||
char socket_file[MAXLEN];
|
||||
snprintf(socket_file, sizeof(socket_file), SOCKET_PATH_FMT, user);
|
||||
|
||||
if (!socket_connect_un(&sockfd, socket_file)) {
|
||||
error("spacebar-msg: failed to connect to socket..\n");
|
||||
}
|
||||
|
||||
int message_length = argc - 1;
|
||||
int argl[argc];
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
argl[i] = strlen(argv[i]);
|
||||
message_length += argl[i];
|
||||
}
|
||||
|
||||
char message[message_length];
|
||||
char *temp = message;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
memcpy(temp, argv[i], argl[i]);
|
||||
temp += argl[i];
|
||||
*temp++ = '\0';
|
||||
}
|
||||
|
||||
if (!socket_write_bytes(sockfd, message, message_length)) {
|
||||
error("spacebar-msg: failed to send data..\n");
|
||||
}
|
||||
|
||||
shutdown(sockfd, SHUT_WR);
|
||||
|
||||
int result = EXIT_SUCCESS;
|
||||
int byte_count = 0;
|
||||
char rsp[BUFSIZ];
|
||||
|
||||
struct pollfd fds[] = {
|
||||
{ sockfd, POLLIN, 0 }
|
||||
};
|
||||
|
||||
while (poll(fds, 1, -1) > 0) {
|
||||
if (fds[0].revents & POLLIN) {
|
||||
if ((byte_count = recv(sockfd, rsp, sizeof(rsp)-1, 0)) <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
rsp[byte_count] = '\0';
|
||||
|
||||
if (rsp[0] == FAILURE_MESSAGE[0]) {
|
||||
result = EXIT_FAILURE;
|
||||
fprintf(stderr, "%s", rsp + 1);
|
||||
fflush(stderr);
|
||||
} else {
|
||||
fprintf(stdout, "%s", rsp);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
socket_close(sockfd);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void acquire_lockfile(void)
|
||||
{
|
||||
int handle = open(g_lock_file, O_CREAT | O_WRONLY, 0600);
|
||||
if (handle == -1) {
|
||||
error("spacebar: could not create lock-file! abort..\n");
|
||||
}
|
||||
|
||||
struct flock lockfd = {
|
||||
.l_start = 0,
|
||||
.l_len = 0,
|
||||
.l_pid = getpid(),
|
||||
.l_type = F_WRLCK,
|
||||
.l_whence = SEEK_SET
|
||||
};
|
||||
|
||||
if (fcntl(handle, F_SETLK, &lockfd) == -1) {
|
||||
error("spacebar: could not acquire lock-file! abort..\n");
|
||||
}
|
||||
}
|
||||
|
||||
static bool get_config_file(char *restrict filename, char *restrict buffer, int buffer_size)
|
||||
{
|
||||
char *xdg_home = getenv("XDG_CONFIG_HOME");
|
||||
if (xdg_home && *xdg_home) {
|
||||
snprintf(buffer, buffer_size, "%s/spacebar/%s", xdg_home, filename);
|
||||
if (file_exists(buffer)) return true;
|
||||
}
|
||||
|
||||
char *home = getenv("HOME");
|
||||
if (!home) return false;
|
||||
|
||||
snprintf(buffer, buffer_size, "%s/.config/spacebar/%s", home, filename);
|
||||
if (file_exists(buffer)) return true;
|
||||
|
||||
snprintf(buffer, buffer_size, "%s/.%s", home, filename);
|
||||
return file_exists(buffer);
|
||||
}
|
||||
|
||||
static void exec_config_file(void)
|
||||
{
|
||||
if (!*g_config_file && !get_config_file("spacebarrc", g_config_file, sizeof(g_config_file))) {
|
||||
notify("configuration", "could not locate config file..");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file_exists(g_config_file)) {
|
||||
notify("configuration", "file '%s' does not exist..", g_config_file);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ensure_executable_permission(g_config_file)) {
|
||||
notify("configuration", "could not set the executable permission bit for '%s'", g_config_file);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fork_exec(g_config_file, NULL)) {
|
||||
notify("configuration", "failed to execute file '%s'", g_config_file);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
static inline void init_misc_settings(void)
|
||||
{
|
||||
char *user = getenv("USER");
|
||||
if (!user) {
|
||||
error("spacebar: 'env USER' not set! abort..\n");
|
||||
}
|
||||
|
||||
snprintf(g_socket_file, sizeof(g_socket_file), SOCKET_PATH_FMT, user);
|
||||
snprintf(g_lock_file, sizeof(g_lock_file), LCFILE_PATH_FMT, user);
|
||||
|
||||
NSApplicationLoad();
|
||||
signal(SIGCHLD, SIG_IGN);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
CGSetLocalEventsSuppressionInterval(0.0f);
|
||||
CGEnableEventStateCombining(false);
|
||||
g_connection = SLSMainConnectionID();
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
static CONNECTION_CALLBACK(connection_handler)
|
||||
{
|
||||
}
|
||||
|
||||
static void parse_arguments(int argc, char **argv)
|
||||
{
|
||||
if ((string_equals(argv[1], VERSION_OPT_LONG)) ||
|
||||
(string_equals(argv[1], VERSION_OPT_SHRT))) {
|
||||
fprintf(stdout, "spacebar-v%d.%d.%d\n", MAJOR, MINOR, PATCH);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
if ((string_equals(argv[1], CLIENT_OPT_LONG)) ||
|
||||
(string_equals(argv[1], CLIENT_OPT_SHRT))) {
|
||||
exit(client_send_message(argc-1, argv+1));
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
char *opt = argv[i];
|
||||
|
||||
if ((string_equals(opt, DEBUG_VERBOSE_OPT_LONG)) ||
|
||||
(string_equals(opt, DEBUG_VERBOSE_OPT_SHRT))) {
|
||||
g_verbose = true;
|
||||
} else if ((string_equals(opt, CONFIG_OPT_LONG)) ||
|
||||
(string_equals(opt, CONFIG_OPT_SHRT))) {
|
||||
char *val = i < argc - 1 ? argv[++i] : NULL;
|
||||
if (!val) error("spacebar: option '%s|%s' requires an argument!\n", CONFIG_OPT_LONG, CONFIG_OPT_SHRT);
|
||||
snprintf(g_config_file, sizeof(g_config_file), "%s", val);
|
||||
} else {
|
||||
error("spacebar: '%s' is not a valid option!\n", opt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc > 1) {
|
||||
parse_arguments(argc, argv);
|
||||
}
|
||||
|
||||
if (is_root()) {
|
||||
error("spacebar: running as root is not allowed! abort..\n");
|
||||
}
|
||||
|
||||
if (!ax_privilege()) {
|
||||
error("spacebar: could not access accessibility features! abort..\n");
|
||||
}
|
||||
|
||||
init_misc_settings();
|
||||
acquire_lockfile();
|
||||
|
||||
if (!event_loop_init(&g_event_loop)) {
|
||||
error("spacebar: could not initialize event_loop! abort..\n");
|
||||
}
|
||||
|
||||
process_manager_init(&g_process_manager);
|
||||
workspace_event_handler_init(&g_workspace_context);
|
||||
application_manager_init(&g_application_manager);
|
||||
bar_manager_init(&g_bar_manager);
|
||||
|
||||
event_loop_begin(&g_event_loop);
|
||||
display_manager_begin(&g_display_manager);
|
||||
process_manager_begin(&g_process_manager);
|
||||
workspace_event_handler_begin(&g_workspace_context);
|
||||
application_manager_begin(&g_application_manager);
|
||||
bar_manager_begin(&g_bar_manager);
|
||||
SLSRegisterConnectionNotifyProc(g_connection, connection_handler, 1204, NULL);
|
||||
|
||||
if (!socket_daemon_begin_un(&g_daemon, g_socket_file, message_handler)) {
|
||||
error("spacebar: could not initialize daemon! abort..\n");
|
||||
}
|
||||
|
||||
exec_config_file();
|
||||
CFRunLoopRun();
|
||||
return 0;
|
||||
}
|
440
src/window.c
Normal file
440
src/window.c
Normal file
|
@ -0,0 +1,440 @@
|
|||
#include "window.h"
|
||||
|
||||
extern int g_connection;
|
||||
extern struct window_manager g_window_manager;
|
||||
|
||||
int g_normal_window_level;
|
||||
int g_floating_window_level;
|
||||
|
||||
static void
|
||||
window_observe_notification(struct window *window, int notification)
|
||||
{
|
||||
AXError result = AXObserverAddNotification(window->application->observer_ref, window->ref, ax_window_notification[notification], window->id_ptr);
|
||||
if (result == kAXErrorSuccess || result == kAXErrorNotificationAlreadyRegistered) window->notification |= 1 << notification;
|
||||
}
|
||||
|
||||
static void
|
||||
window_unobserve_notification(struct window *window, int notification)
|
||||
{
|
||||
AXObserverRemoveNotification(window->application->observer_ref, window->ref, ax_window_notification[notification]);
|
||||
window->notification &= ~(1 << notification);
|
||||
}
|
||||
|
||||
bool window_observe(struct window *window)
|
||||
{
|
||||
for (int i = 0; i < array_count(ax_window_notification); ++i) {
|
||||
window_observe_notification(window, i);
|
||||
}
|
||||
|
||||
return (window->notification & AX_WINDOW_ALL) == AX_WINDOW_ALL;
|
||||
}
|
||||
|
||||
void window_unobserve(struct window *window)
|
||||
{
|
||||
for (int i = 0; i < array_count(ax_window_notification); ++i) {
|
||||
if (!(window->notification & (1 << i))) continue;
|
||||
window_unobserve_notification(window, i);
|
||||
}
|
||||
}
|
||||
|
||||
CFStringRef window_display_uuid(struct window *window)
|
||||
{
|
||||
CFStringRef uuid = SLSCopyManagedDisplayForWindow(g_connection, window->id);
|
||||
if (!uuid) {
|
||||
CGRect frame = window_frame(window);
|
||||
uuid = SLSCopyBestManagedDisplayForRect(g_connection, frame);
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
int window_display_id(struct window *window)
|
||||
{
|
||||
CFStringRef uuid_string = window_display_uuid(window);
|
||||
if (!uuid_string) return 0;
|
||||
|
||||
CFUUIDRef uuid = CFUUIDCreateFromString(NULL, uuid_string);
|
||||
int id = CGDisplayGetDisplayIDFromUUID(uuid);
|
||||
|
||||
CFRelease(uuid);
|
||||
CFRelease(uuid_string);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
uint64_t window_space(struct window *window)
|
||||
{
|
||||
uint64_t sid = 0;
|
||||
CFArrayRef window_list_ref = cfarray_of_cfnumbers(&window->id, sizeof(uint32_t), 1, kCFNumberSInt32Type);
|
||||
CFArrayRef space_list_ref = SLSCopySpacesForWindows(g_connection, 0x7, window_list_ref);
|
||||
if (!space_list_ref) goto err;
|
||||
|
||||
int count = CFArrayGetCount(space_list_ref);
|
||||
if (count) {
|
||||
CFNumberRef id_ref = CFArrayGetValueAtIndex(space_list_ref, 0);
|
||||
CFNumberGetValue(id_ref, CFNumberGetType(id_ref), &sid);
|
||||
}
|
||||
|
||||
CFRelease(space_list_ref);
|
||||
err:
|
||||
CFRelease(window_list_ref);
|
||||
return sid;
|
||||
}
|
||||
|
||||
uint64_t *window_space_list(struct window *window, int *count)
|
||||
{
|
||||
uint64_t *space_list = NULL;
|
||||
CFArrayRef window_list_ref = cfarray_of_cfnumbers(&window->id, sizeof(uint32_t), 1, kCFNumberSInt32Type);
|
||||
CFArrayRef space_list_ref = SLSCopySpacesForWindows(g_connection, 0x7, window_list_ref);
|
||||
if (!space_list_ref) goto err;
|
||||
|
||||
*count = CFArrayGetCount(space_list_ref);
|
||||
if (!*count) goto out;
|
||||
|
||||
space_list = malloc(*count * sizeof(uint64_t));
|
||||
for (int i = 0; i < *count; ++i) {
|
||||
CFNumberRef id_ref = CFArrayGetValueAtIndex(space_list_ref, i);
|
||||
CFNumberGetValue(id_ref, CFNumberGetType(id_ref), space_list + i);
|
||||
}
|
||||
|
||||
out:
|
||||
CFRelease(space_list_ref);
|
||||
err:
|
||||
CFRelease(window_list_ref);
|
||||
return space_list;
|
||||
}
|
||||
|
||||
void window_serialize(FILE *rsp, struct window *window)
|
||||
{
|
||||
char *title = window_title(window);
|
||||
char *escaped_title = string_escape_quote(title);
|
||||
CGRect frame = window_frame(window);
|
||||
char *role = NULL;
|
||||
char *subrole = NULL;
|
||||
bool sticky = window_is_sticky(window);
|
||||
uint64_t sid = window_space(window);
|
||||
int space = space_manager_mission_control_index(sid);
|
||||
int display = display_arrangement(space_display_id(sid));
|
||||
bool visible = sticky || space_is_visible(sid);
|
||||
bool is_topmost = window_is_topmost(window);
|
||||
|
||||
CFStringRef cfrole = window_role(window);
|
||||
if (cfrole) {
|
||||
role = cfstring_copy(cfrole);
|
||||
CFRelease(cfrole);
|
||||
}
|
||||
|
||||
CFStringRef cfsubrole = window_subrole(window);
|
||||
if (cfsubrole) {
|
||||
subrole = cfstring_copy(cfsubrole);
|
||||
CFRelease(cfsubrole);
|
||||
}
|
||||
|
||||
struct view *view = window_manager_find_managed_window(&g_window_manager, window);
|
||||
struct window_node *node = view ? view_find_window_node(view, window->id) : NULL;
|
||||
|
||||
char split[MAXLEN];
|
||||
snprintf(split, sizeof(split), "%s", window_node_split_str[node && node->parent ? node->parent->split : 0]);
|
||||
bool zoom_parent = node && node->zoom && node->zoom == node->parent;
|
||||
bool zoom_fullscreen = node && node->zoom && node->zoom == view->root;
|
||||
|
||||
fprintf(rsp,
|
||||
"{\n"
|
||||
"\t\"id\":%d,\n"
|
||||
"\t\"pid\":%d,\n"
|
||||
"\t\"app\":\"%s\",\n"
|
||||
"\t\"title\":\"%s\",\n"
|
||||
"\t\"frame\":{\n\t\t\"x\":%.4f,\n\t\t\"y\":%.4f,\n\t\t\"w\":%.4f,\n\t\t\"h\":%.4f\n\t},\n"
|
||||
"\t\"level\":%d,\n"
|
||||
"\t\"role\":\"%s\",\n"
|
||||
"\t\"subrole\":\"%s\",\n"
|
||||
"\t\"movable\":%d,\n"
|
||||
"\t\"resizable\":%d,\n"
|
||||
"\t\"display\":%d,\n"
|
||||
"\t\"space\":%d,\n"
|
||||
"\t\"visible\":%d,\n"
|
||||
"\t\"focused\":%d,\n"
|
||||
"\t\"split\":\"%s\",\n"
|
||||
"\t\"floating\":%d,\n"
|
||||
"\t\"sticky\":%d,\n"
|
||||
"\t\"topmost\":%d,\n"
|
||||
"\t\"border\":%d,\n"
|
||||
"\t\"shadow\":%d,\n"
|
||||
"\t\"zoom-parent\":%d,\n"
|
||||
"\t\"zoom-fullscreen\":%d,\n"
|
||||
"\t\"native-fullscreen\":%d\n"
|
||||
"}",
|
||||
window->id,
|
||||
window->application->pid,
|
||||
window->application->name,
|
||||
escaped_title ? escaped_title : title ? title : "",
|
||||
frame.origin.x, frame.origin.y,
|
||||
frame.size.width, frame.size.height,
|
||||
window_level(window),
|
||||
role ? role : "",
|
||||
subrole ? subrole : "",
|
||||
window_can_move(window),
|
||||
window_can_resize(window),
|
||||
display,
|
||||
space,
|
||||
visible,
|
||||
window->id == g_window_manager.focused_window_id,
|
||||
split,
|
||||
window->is_floating,
|
||||
sticky,
|
||||
is_topmost,
|
||||
window->border.enabled,
|
||||
window->has_shadow,
|
||||
zoom_parent,
|
||||
zoom_fullscreen,
|
||||
window_is_fullscreen(window));
|
||||
|
||||
if (subrole) free(subrole);
|
||||
if (role) free(role);
|
||||
if (title) free(title);
|
||||
if (escaped_title) free(escaped_title);
|
||||
}
|
||||
|
||||
char *window_title(struct window *window)
|
||||
{
|
||||
char *title = NULL;
|
||||
CFTypeRef value = NULL;
|
||||
|
||||
#if 0
|
||||
SLSCopyWindowProperty(g_connection, window->id, CFSTR("kCGSWindowTitle"), &value);
|
||||
#else
|
||||
AXUIElementCopyAttributeValue(window->ref, kAXTitleAttribute, &value);
|
||||
#endif
|
||||
|
||||
if (value) {
|
||||
title = cfstring_copy(value);
|
||||
CFRelease(value);
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
CGRect window_ax_frame(struct window *window)
|
||||
{
|
||||
CGRect frame = {};
|
||||
CFTypeRef position_ref = NULL;
|
||||
CFTypeRef size_ref = NULL;
|
||||
|
||||
AXUIElementCopyAttributeValue(window->ref, kAXPositionAttribute, &position_ref);
|
||||
AXUIElementCopyAttributeValue(window->ref, kAXSizeAttribute, &size_ref);
|
||||
|
||||
if (position_ref != NULL) {
|
||||
AXValueGetValue(position_ref, kAXValueTypeCGPoint, &frame.origin);
|
||||
CFRelease(position_ref);
|
||||
}
|
||||
|
||||
if (size_ref != NULL) {
|
||||
AXValueGetValue(size_ref, kAXValueTypeCGSize, &frame.size);
|
||||
CFRelease(size_ref);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
CGRect window_frame(struct window *window)
|
||||
{
|
||||
CGRect frame = {};
|
||||
SLSGetWindowBounds(g_connection, window->id, &frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool window_can_move(struct window *window)
|
||||
{
|
||||
Boolean result;
|
||||
if (AXUIElementIsAttributeSettable(window->ref, kAXPositionAttribute, &result) != kAXErrorSuccess) {
|
||||
result = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool window_can_resize(struct window *window)
|
||||
{
|
||||
Boolean result;
|
||||
if (AXUIElementIsAttributeSettable(window->ref, kAXSizeAttribute, &result) != kAXErrorSuccess) {
|
||||
result = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool window_is_undersized(struct window *window)
|
||||
{
|
||||
CGRect frame = window_frame(window);
|
||||
if (frame.size.width < 200.0f) return true;
|
||||
if (frame.size.height < 200.0f) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool window_is_minimized(struct window *window)
|
||||
{
|
||||
Boolean result = 0;
|
||||
CFTypeRef value;
|
||||
|
||||
if (AXUIElementCopyAttributeValue(window->ref, kAXMinimizedAttribute, &value) == kAXErrorSuccess) {
|
||||
result = CFBooleanGetValue(value);
|
||||
CFRelease(value);
|
||||
}
|
||||
|
||||
return result || window->is_minimized;
|
||||
}
|
||||
|
||||
bool window_is_fullscreen(struct window *window)
|
||||
{
|
||||
Boolean result = 0;
|
||||
CFTypeRef value;
|
||||
if (AXUIElementCopyAttributeValue(window->ref, kAXFullscreenAttribute, &value) == kAXErrorSuccess) {
|
||||
result = CFBooleanGetValue(value);
|
||||
CFRelease(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool window_is_sticky(struct window *window)
|
||||
{
|
||||
bool result = false;
|
||||
CFArrayRef window_list_ref = cfarray_of_cfnumbers(&window->id, sizeof(uint32_t), 1, kCFNumberSInt32Type);
|
||||
CFArrayRef space_list_ref = SLSCopySpacesForWindows(g_connection, 0x7, window_list_ref);
|
||||
if (!space_list_ref) goto err;
|
||||
|
||||
result = CFArrayGetCount(space_list_ref) > 1;
|
||||
|
||||
CFRelease(space_list_ref);
|
||||
err:
|
||||
CFRelease(window_list_ref);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool window_is_topmost(struct window *window)
|
||||
{
|
||||
bool is_topmost = window_level(window) == CGWindowLevelForKey(LAYER_ABOVE);
|
||||
return is_topmost;
|
||||
}
|
||||
|
||||
int window_level(struct window *window)
|
||||
{
|
||||
int level = 0;
|
||||
SLSGetWindowLevel(g_connection, window->id, &level);
|
||||
return level;
|
||||
}
|
||||
|
||||
CFStringRef window_role(struct window *window)
|
||||
{
|
||||
const void *role = NULL;
|
||||
AXUIElementCopyAttributeValue(window->ref, kAXRoleAttribute, &role);
|
||||
return role;
|
||||
}
|
||||
|
||||
CFStringRef window_subrole(struct window *window)
|
||||
{
|
||||
const void *srole = NULL;
|
||||
AXUIElementCopyAttributeValue(window->ref, kAXSubroleAttribute, &srole);
|
||||
return srole;
|
||||
}
|
||||
|
||||
bool window_level_is_standard(struct window *window)
|
||||
{
|
||||
if (!g_normal_window_level) g_normal_window_level = CGWindowLevelForKey(4);
|
||||
if (!g_floating_window_level) g_floating_window_level = CGWindowLevelForKey(5);
|
||||
|
||||
int level = window_level(window);
|
||||
return level == g_normal_window_level || level == g_floating_window_level;
|
||||
}
|
||||
|
||||
bool window_is_standard(struct window *window)
|
||||
{
|
||||
bool standard_win = false;
|
||||
CFStringRef role = NULL;
|
||||
CFStringRef srole = NULL;
|
||||
|
||||
if (!(role = window_role(window))) goto out;
|
||||
if (!(srole = window_subrole(window))) goto role;
|
||||
|
||||
standard_win = CFEqual(role, kAXWindowRole) &&
|
||||
CFEqual(srole, kAXStandardWindowSubrole);
|
||||
|
||||
CFRelease(srole);
|
||||
role:
|
||||
CFRelease(role);
|
||||
out:
|
||||
return standard_win;
|
||||
}
|
||||
|
||||
bool window_is_dialog(struct window *window)
|
||||
{
|
||||
bool standard_win = false;
|
||||
CFStringRef role = NULL;
|
||||
CFStringRef srole = NULL;
|
||||
|
||||
if (!(role = window_role(window))) goto out;
|
||||
if (!(srole = window_subrole(window))) goto role;
|
||||
|
||||
standard_win = CFEqual(role, kAXWindowRole) &&
|
||||
CFEqual(srole, kAXDialogSubrole);
|
||||
|
||||
CFRelease(srole);
|
||||
role:
|
||||
CFRelease(role);
|
||||
out:
|
||||
return standard_win;
|
||||
}
|
||||
|
||||
bool window_is_popover(struct window *window)
|
||||
{
|
||||
CFStringRef role = window_role(window);
|
||||
if (!role) return false;
|
||||
|
||||
bool result = CFEqual(role, kAXPopoverRole);
|
||||
CFRelease(role);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool window_is_unknown(struct window *window)
|
||||
{
|
||||
CFStringRef subrole = window_subrole(window);
|
||||
if (!subrole) return false;
|
||||
|
||||
bool result = CFEqual(subrole, kAXUnknownSubrole);
|
||||
CFRelease(subrole);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct window *window_create(struct application *application, AXUIElementRef window_ref, uint32_t window_id)
|
||||
{
|
||||
struct window *window = malloc(sizeof(struct window));
|
||||
memset(window, 0, sizeof(struct window));
|
||||
|
||||
window->application = application;
|
||||
window->ref = window_ref;
|
||||
window->id = window_id;
|
||||
SLSGetWindowOwner(g_connection, window->id, &window->connection);
|
||||
window->is_minimized = window_is_minimized(window);
|
||||
window->is_fullscreen = window_is_fullscreen(window) || space_is_fullscreen(window_space(window));
|
||||
window->id_ptr = malloc(sizeof(uint32_t *));
|
||||
*window->id_ptr = &window->id;
|
||||
window->has_shadow = true;
|
||||
|
||||
if ((window_is_standard(window)) || (window_is_dialog(window))) {
|
||||
border_window_create(window);
|
||||
|
||||
if ((!window->application->is_hidden) &&
|
||||
(!window->is_minimized) &&
|
||||
(!window->is_fullscreen)) {
|
||||
border_window_refresh(window);
|
||||
}
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
void window_destroy(struct window *window)
|
||||
{
|
||||
border_window_destroy(window);
|
||||
CFRelease(window->ref);
|
||||
free(window->id_ptr);
|
||||
free(window);
|
||||
}
|
78
src/window.h
Normal file
78
src/window.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
#ifndef WINDOW_H
|
||||
#define WINDOW_H
|
||||
|
||||
extern int SLSMainConnectionID(void);
|
||||
extern CGError SLSGetWindowBounds(int cid, uint32_t wid, CGRect *frame);
|
||||
extern CGError SLSGetWindowLevel(int cid, uint32_t wid, int *level);
|
||||
extern CGError SLSCopyWindowProperty(int cid, uint32_t wid, CFStringRef property, CFTypeRef *value);
|
||||
extern CFStringRef SLSCopyManagedDisplayForWindow(int cid, uint32_t wid);
|
||||
extern CFStringRef SLSCopyBestManagedDisplayForRect(int cid, CGRect rect);
|
||||
extern CFArrayRef SLSCopySpacesForWindows(int cid, int selector, CFArrayRef window_list);
|
||||
|
||||
const CFStringRef kAXFullscreenAttribute = CFSTR("AXFullScreen");
|
||||
|
||||
#define AX_WINDOW_MINIMIZED_INDEX 0
|
||||
#define AX_WINDOW_DEMINIMIZED_INDEX 1
|
||||
#define AX_WINDOW_DESTROYED_INDEX 2
|
||||
|
||||
#define AX_WINDOW_DESTROYED (1 << AX_WINDOW_DESTROYED_INDEX)
|
||||
#define AX_WINDOW_MINIMIZED (1 << AX_WINDOW_MINIMIZED_INDEX)
|
||||
#define AX_WINDOW_DEMINIMIZED (1 << AX_WINDOW_DEMINIMIZED_INDEX)
|
||||
#define AX_WINDOW_ALL (AX_WINDOW_DESTROYED |\
|
||||
AX_WINDOW_MINIMIZED |\
|
||||
AX_WINDOW_DEMINIMIZED)
|
||||
|
||||
static CFStringRef ax_window_notification[] =
|
||||
{
|
||||
[AX_WINDOW_DESTROYED_INDEX] = kAXUIElementDestroyedNotification,
|
||||
[AX_WINDOW_MINIMIZED_INDEX] = kAXWindowMiniaturizedNotification,
|
||||
[AX_WINDOW_DEMINIMIZED_INDEX] = kAXWindowDeminiaturizedNotification
|
||||
};
|
||||
|
||||
struct window
|
||||
{
|
||||
struct application *application;
|
||||
AXUIElementRef ref;
|
||||
int connection;
|
||||
uint32_t id;
|
||||
uint32_t **volatile id_ptr;
|
||||
uint8_t notification;
|
||||
struct border border;
|
||||
bool has_shadow;
|
||||
bool is_fullscreen;
|
||||
bool is_minimized;
|
||||
bool is_floating;
|
||||
float rule_alpha;
|
||||
bool rule_manage;
|
||||
bool rule_fullscreen;
|
||||
};
|
||||
|
||||
CFStringRef window_display_uuid(struct window *window);
|
||||
int window_display_id(struct window *window);
|
||||
uint64_t window_space(struct window *window);
|
||||
uint64_t *window_space_list(struct window *window, int *count);
|
||||
void window_serialize(FILE *rsp, struct window *window);
|
||||
char *window_title(struct window *window);
|
||||
CGRect window_ax_frame(struct window *window);
|
||||
CGRect window_frame(struct window *window);
|
||||
int window_level(struct window *window);
|
||||
CFStringRef window_role(struct window *window);
|
||||
CFStringRef window_subrole(struct window *window);
|
||||
bool window_can_move(struct window *window);
|
||||
bool window_can_resize(struct window *window);
|
||||
bool window_level_is_standard(struct window *window);
|
||||
bool window_is_undersized(struct window *window);
|
||||
bool window_is_minimized(struct window *window);
|
||||
bool window_is_fullscreen(struct window *window);
|
||||
bool window_is_sticky(struct window *window);
|
||||
bool window_is_topmost(struct window *window);
|
||||
bool window_is_standard(struct window *window);
|
||||
bool window_is_dialog(struct window *window);
|
||||
bool window_is_popover(struct window *window);
|
||||
bool window_is_unknown(struct window *window);
|
||||
bool window_observe(struct window *window);
|
||||
void window_unobserve(struct window *window);
|
||||
struct window *window_create(struct application *application, AXUIElementRef window_ref, uint32_t window_id);
|
||||
void window_destroy(struct window *window);
|
||||
|
||||
#endif
|
13
src/workspace.h
Normal file
13
src/workspace.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifndef WORKSPACE_H
|
||||
#define WORKSPACE_H
|
||||
|
||||
@interface workspace_context : NSObject {
|
||||
}
|
||||
- (id)init;
|
||||
@end
|
||||
|
||||
void workspace_event_handler_init(void **context);
|
||||
void workspace_event_handler_begin(void **context);
|
||||
void workspace_event_handler_end(void *context);
|
||||
|
||||
#endif
|
83
src/workspace.m
Normal file
83
src/workspace.m
Normal file
|
@ -0,0 +1,83 @@
|
|||
#include "workspace.h"
|
||||
|
||||
extern struct event_loop g_event_loop;
|
||||
|
||||
void workspace_event_handler_init(void **context)
|
||||
{
|
||||
workspace_context *ws_context = [workspace_context alloc];
|
||||
*context = ws_context;
|
||||
}
|
||||
|
||||
void workspace_event_handler_begin(void **context)
|
||||
{
|
||||
workspace_context *ws_context = *context;
|
||||
[ws_context init];
|
||||
}
|
||||
|
||||
void workspace_event_handler_end(void *context)
|
||||
{
|
||||
workspace_context *ws_context = (workspace_context *) context;
|
||||
[ws_context dealloc];
|
||||
}
|
||||
|
||||
@implementation workspace_context
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
|
||||
selector:@selector(activeDisplayDidChange:)
|
||||
name:@"NSWorkspaceActiveDisplayDidChangeNotification"
|
||||
object:nil];
|
||||
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
|
||||
selector:@selector(activeSpaceDidChange:)
|
||||
name:NSWorkspaceActiveSpaceDidChangeNotification
|
||||
object:nil];
|
||||
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
|
||||
selector:@selector(didWake:)
|
||||
name:NSWorkspaceDidWakeNotification
|
||||
object:nil];
|
||||
|
||||
[[NSDistributedNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(didChangeMenuBarHiding:)
|
||||
name:@"AppleInterfaceMenuBarHidingChangedNotification"
|
||||
object:nil];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)didWake:(NSNotification *)notification
|
||||
{
|
||||
struct event *event = event_create(&g_event_loop, SYSTEM_WOKE, NULL);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
}
|
||||
|
||||
- (void)didChangeMenuBarHiding:(NSNotification *)notification
|
||||
{
|
||||
struct event *event = event_create(&g_event_loop, MENU_BAR_HIDDEN_CHANGED, NULL);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
}
|
||||
|
||||
- (void)activeDisplayDidChange:(NSNotification *)notification
|
||||
{
|
||||
struct event *event = event_create(&g_event_loop, DISPLAY_CHANGED, NULL);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
}
|
||||
|
||||
- (void)activeSpaceDidChange:(NSNotification *)notification
|
||||
{
|
||||
struct event *event = event_create(&g_event_loop, SPACE_CHANGED, NULL);
|
||||
event_loop_post(&g_event_loop, event);
|
||||
}
|
||||
|
||||
@end
|
Loading…
Reference in a new issue